Spring 源码学习(七)扩展功能 下篇(一)

简介: 在前面的 Spring 源码学习系列的文章中,深入分析和学习了 BeanFactoryPostProcessor ,主体是 BeanFactory 的后处理器,这次来学习主体是 Bean 的后处理器:BeanPostProcessor。

定义:它也是 Spring 对外提供的接口,用来给用户扩展自定义的功能。执行的时机在 bean 实例化阶段前后

本篇思路:

  1. BeanPostProcessor 定义
  2. 如何使用
  3. 代码实现分析
  4. 介绍剩余的扩展功能


前言

BeanFactoryPostProcessor 不同的是,BeanFactoryPostProcessor 的注册和执行都在同一个方法内,而 BeanPostProcessor 分开两个方法,分为注册调用两个步骤。

常规的 BeanFactory 中是没有实现后处理器的自动注册,所以在调用的时候没有进行手动注册是无法使用的,但在 ApplicationContext 中添加了自动注册功能(在这个 registerBeanPostProcessors 方法中),最后在 bean 实例化时执行 BeanPostProcessor 对应的方法。

本次主要介绍 BeanPostProcessor,同时也会将剩下的 context 扩展功能一起学习~


BeanPostProcessor

经过上一篇文章的学习,应该对 bean 的后处理理解起来更顺利,下面直奔主题,来看下它是如何使用和结合源码分析

如何使用

新建一个 bean 后处理器

这个后处理器需要引用 InstantiationAwareBeanPostProcessor 接口(实际继承自 BeanPostProcessor),然后重载以下两个方法:

public class CarBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 这里没有区分 bean 类型,只是用来测试打印的顺序和时间
        System.out.println("Bean name : " + beanName + ", before, time : " + System.currentTimeMillis());
        return null;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean name : " + beanName + ", after time : " + System.currentTimeMillis());
        return null;
    }
}

在配置文件中注册 bean-post-processor.xml

在配置文件配置我们写的自定义后处理器和两个普通 bean,用来测试打印时间和顺序

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <!-- beanPostProcessor -->
  <bean id="carPostProcessor" class="context.bean.CarBeanPostProcessor"/>
  <!--用以下两个 bean 进行测试打印时间和顺序-->
  <bean id="car" class="base.factory.bean.Car">
    <property name="price" value="10000"/>
    <property name="brand" value="奔驰"/>
  </bean>
  <bean id="book" class="domain.ComplexBook"/>
</beans>

启动代码和打印结果

public class CarBeanPostProcessorBootstrap {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/bean-post-processor.xml");
    Car car = (Car) context.getBean("car");
    ComplexBook book = (ComplexBook) context.getBean("book");
    System.out.println(car);
    System.out.println(book);
  }
}

输出:

Bean name : car, before Initialization, time : 1560772863996
Bean name : car, after Initialization, time : 1560772863996
Bean name : book, before Initialization, time : 1560772863999
Bean name : book, after Initialization, time : 1560772863999
Car{maxSpeed=0, brand='奔驰', price=10000.0}
domain.ComplexBook@77be656f

从输出接口看出,打印顺序是先框架内部,再到应用层,框架内部中,在顺序实例化每个 bean 时,前面也提到执行时机:先执行 postProcessBeforeInitialization 方法,然后实例化 bean后,执行 postProcessAfterInitialization

所以我们重载的两个接口按照前后顺序打印出来了~

注册 BeanPostProcessor

上面介绍了使用例子,应该不难理解,接着来看下源码注册的方法:

org.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors


实际委托给了 PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);

public static void registerBeanPostProcessors(
      ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
    // 注释 7.2 从注册表中取出 class 类型为 BeanPostProcessor 的 bean 名称列表
    String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
    beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
    // 将带有 权限顺序、顺序和其余的 beanPostProcessor 分开
    List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    // 类型是 MergedBeanDefinitionPostProcessor
    List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
    List<String> orderedPostProcessorNames = new ArrayList<>();
    List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    for (String ppName : postProcessorNames) {
            // 分类,添加到对应数组中
      ...
    }
    // 首先,注册实现了 PriorityOrdered 接口的 bean 后处理器
    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
    // 下一步,注册实现了 Ordered 接口的 bean 后处理器
    List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
    for (String ppName : orderedPostProcessorNames) {
      BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
      orderedPostProcessors.add(pp);
      if (pp instanceof MergedBeanDefinitionPostProcessor) {
        internalPostProcessors.add(pp);
      }
    }
    sortPostProcessors(orderedPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, orderedPostProcessors);
    // 现在,注册常规 bean 后处理器,其实就是不带顺序
    List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
    for (String ppName : nonOrderedPostProcessorNames) {
      BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
      nonOrderedPostProcessors.add(pp);
      if (pp instanceof MergedBeanDefinitionPostProcessor) {
        internalPostProcessors.add(pp);
      }
    }
    registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
    // 最后,重新注册 MergedBeanDefinitionPostProcessor 类型的后处理器
    // 看起来是重复注册了,但是每次注册调用的底层方法都会先移除已存在的 beanPostProcessor,然后再加进去,最后还是保存唯一
    sortPostProcessors(internalPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, internalPostProcessors);
    // 添加 ApplicationContext 探测器
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
  }

跟之前的 BeanFactoryPostProcessor 处理是不是很相似,也是进行分类,将带有权重顺序、顺序和普通 BeanPostProcessor 添加到对应的列表后,然后排序,统一注册到 beanPostProcessors 列表末尾。

BeanPostProcessor 与之前的 BeanFactoryPostProcessor 进行对比后发现,少了硬编码注册的代码,只处理了配置文件方式的注册 bean。通过书中阐释,对少了硬编码的处理有些理解:


对于 BeanFactoryPostProcessor 的处理,在一个方法内实现了注册和实现,所以需要载入配置中的定义,并进行激活;而对于 BeanPostProcessor 并不需要马上调用,硬编码的方式实现的功能是将后处理器提取并调用,对于 BeanPostProcessor,注册阶段不需要调用,所以没有考虑处理硬编码,在这里只需要将配置文件的 BeanPostProcessor 提取出来并注册进入 beanFactory 就可以了。


而且我在测试过程,想在应用代码中进行硬编码注册,发现由于 ClassPathXmlApplicationContext 最后一个方法是实例化非延迟加载的 bean,在上下文创建好时,BeanPostProcessor 就已经执行完成了,于是硬编码注册的后处理器无法执行,只能通过设定延迟加载或者在配置文件配置中进行注册,或者其它 BeanFactory 能支持硬编码。

剩下顺序 Order 类型的后处理器注册 BeanFactoryPostProcessor 类似就不重复多讲解了,这段代码的逻辑挺清晰的~


小结

结束两个扩展功能,BeanFactoryPostProcessorBeanPostProcessor 的学习使用后,还有其它的扩展功能没学习到,在一开始基础机构篇就提到剩下的方法:

17.jpg

这这些扩展功能中,个人感觉事件传播器、监听器和发送广播事件这三个会用得比较多,所以下面的内容会花比较大篇幅讲这三个扩展。

相关文章
|
18天前
|
XML 安全 Java
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
50 0
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
114 62
|
18天前
|
XML Java 数据格式
Spring Core核心类库的功能与应用实践分析
【12月更文挑战第1天】大家好,今天我们来聊聊Spring Core这个强大的核心类库。Spring Core作为Spring框架的基础,提供了控制反转(IOC)和依赖注入(DI)等核心功能,以及企业级功能,如JNDI和定时任务等。通过本文,我们将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring Core,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
42 14
|
16天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
38 2
|
1月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
63 9
|
1月前
|
消息中间件 缓存 Java
手写模拟Spring Boot启动过程功能
【11月更文挑战第19天】Spring Boot自推出以来,因其简化了Spring应用的初始搭建和开发过程,迅速成为Java企业级应用开发的首选框架之一。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,帮助读者深入理解其工作机制。
42 3
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
80 2