首页 > 编程语言 >Spring源码分析(八)容器的扩展点(BeanPostProcessor)

Spring源码分析(八)容器的扩展点(BeanPostProcessor)

时间:2023-09-10 21:32:52浏览次数:41  
标签:BeanPostProcessor 后置 容器 Spring bean 源码 处理器

在前面两篇关于容器扩展的文章,我们已经完成了对 BeanFactoryPostProcessor 和 FactoryBean 的分析,对于 BeanFactoryPostProcessor 而言,它能让我们对容器中扫描出来的 BeanDefinition 做出修改以达到扩展的目的,而对于 FactoryBean 而言,它提供了一种特殊创建 bean 的手段,能让我们将一个对象直接放入容器中,成为 Spring 所管理的一个 bean。而这篇文章将要说的 BeanPostProcessor 不同于上面两个接口,它主要干预的是 Spring 中 bean 的整个生命周期(实例化---属性填充---初始化---销毁),关于 bean 的生命周期将在下篇文章中介绍。

按照管理,先看官网对 BeanPostProcessor 的介绍

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_嵌套

从这段文字中,我们能获取到如下信息:

  1. BeanPostProcessor 接口定义了两个回调方法,通过实现这两个方法我们可以提供自己的实例化以及依赖注入逻辑。而且,如果我们想要 Spring 容器完成实例化,配置以及初始化一个 bean 后进行一些定制的逻辑,我们可以插入一个甚至多个 BeanPostProcessor 的实现。
  2. 我们可以配置多个 BeanPostProcessor,并且只有我们配置的 BeanFactoryPostProcessor 同时实现了 Ordered 接口的话,还可以控制这些 BeanPostProcessor 执行的顺序。

我们通过一个例子来看看 BeanPostProcessor 的作用

应用举例

demo:

// 自己实现了一个 BeanPostProcessor
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      if (beanName.equals("indexService")) {
         System.out.println(bean);
         System.out.println("bean config invoke postProcessBeforeInitialization");
      }
      return bean;
   }


   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      if (beanName.equals("indexService")) {
         System.out.println(bean);
         System.out.println("bean config invoke postProcessAfterInitialization");
      }
      return bean;
   }
}


@Component
public class IndexService {
   @Autowired
   LuBanService luBanService;


   @Override
   public String toString() {
      return "IndexService{" +
            "luBanService=" + luBanService +
            '}';
   }
}


public class Main {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
   }
}

运行上面的程序:

IndexService{luBanService=com.dmz.official.extension.entity.LuBanService@5e025e70}
bean config invoke postProcessBeforeInitialization
IndexService{luBanService=com.dmz.official.extension.entity.LuBanService@5e025e70}
bean config invoke postProcessAfterInitialization

从上面的执行结果可以得出一个结论,BeanPostProcessor 接口中的两个方法的执行时机在属性注入之后。因为从打印的结果可以发现,IndexService 中的 luBanService 属性以及被注入了。

接口继承关系

由于 BeanPostProcessor 这个接口 Spring 本身内置的实现类有很多,所以这里暂且不分析其实现类,就从接口的定义上来分析它的作用,其接口的 UML 类图如下:

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_ci_02

  1. BeanPostProcessor,这个接口是我们 bean 的后置处理器的顶级接口,其中主要包含了两个方法
// 在 bean 初始化前调用
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
   return bean;
}


// 在 bean 初始化后调用
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   return bean;
}
  1. InstantiationAwareBeanPostProcessor,继承了 BeanPostProcessor 接口,并在此基础上扩展了 4 个方法,其中方法 postProcessPropertyValues 以及在 5.1 版本中废弃:

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_嵌套_03

大部分情况下我们在扩展时都不会用到 postProcessProperties 和 postProcessPropertyValues,如果在某些场景下不得不用到这两个方法,那么请注意,在实现 postProcessProperties 必须返回 null,否则 postProcessPropertyValues 的逻辑不会只想。

  1. SmartInstantiationAwareBeanPostProcessor,继续扩展了上面的接口,并多提供了三个方法:

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_生命周期_04

这个接口的三个方法一般是在 Spring 内部使用,可以关注这个接口上的一段 Java doc

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_嵌套_05

上面这段文字很明确的指出了这个接口的设计是为了一些特殊的目的,主要是在 Spring 框架内部使用,通常来说我们提供的后置处理器只有实现 BeanPostProcessor 或者 InstantiationAwareBeanPostProcessorAdapter 即可。正常情况下,我们在扩展时不需要考虑着几个方法。

  1. DestructionAwareBeanPostProcessor,这个接口直接继承了 BeanPostProcessor,同时多提供了两个方法,主要用于 bean 在进行销毁时进行回调

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_ci_06

  1. MergedBeanDefinitionPostProcessor,这个接口也直接继承了 BeanPostProcessor,但是多提供了两个方法。

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_嵌套_07

源码分析

我们带着两个问题去阅读源码:

  1. 容器这么多 BeanPostProcessor,它们是按什么顺序执行的?
  2. BeanP 接口中这么多方法,它们的执行时机是什么时候?

接下来解决这两个问题

执行顺序

在 Spring 内部,当去执行一个 BeanPostProcessor 一般都是采用下面这种形式的代码:

for (BeanPostProcessor bp : getBeanPostProcessors()) {
      // 判断属于某一类后置处理器
      if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
         // 执行逻辑
      }
   }
}

getBeanPostProcessors()获取到的 BeanPostProcessor 其实就是一个 list 集合,所以我们要分析 BeanPostProcessor 的执行顺序,其实就是分析这个 list 集合中的数据是通过什么顺序添加进来的,先来看看之前说的 Spring 的执行流程图:

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_ci_08

我们这次要分析的代码就是其中的 3-6 步骤,代码如下:

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_嵌套_09

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_ci_10

疑惑代码解读

下面对上述代码进行一波分析:

  1. 获取容器中已经注册的 bean 的名称,根据 BeanDefinition 中获取 BeanName

这里主要是根据已经注册在容器中的 BeanDefinition,这些 BeanDefinition 即包括程序员自己注册到容器中的,也包括 Spring 自己注册到容器中。注意这些后置处理器目前没有被创建,只是以 BeanDefinition 的形式存在于容器,所以如果此时调用 getBeanPostProcessors(),是拿不到这些后置处理器的,至于容器是什么时候注册了后置处理器的 BeanDefinition,大家可以先自行阅读 1-1 步骤的源码,我在后续文章中会分析,当前就暂时先跳过了

  1. 通过addBeanPostProcessor方法添加的BeanPostProcessor以及注册到容器中的BeanPostProcessor的总数量

这里主要是获取容器中已经存在的BeanPostProcessor的数量再加上已经被扫描出来的BeanDefinition的后置处理器的属性(这些后置处理器还没有被创建出来),最后加1.这里主要两个问题:

  • 容器已经存在的BeanPostProcessor是从哪里来的?

分为两个来源,第一,容器启动时,自身调用了addBeanPostProcessor添加了后置处理器;第二,程序员手动调用了addBeanPostProcessor方法添加了后置处理器。第二种情况很少见,代码如下面这种形式:

public static void main(String[] args) {
   AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
   ac.register(Appconfig.class);
   ac.getBeanFactory().addBeanPostProcessor(new MyBeanPostProcessor());
   ac.refresh();
}

容器又是在什么时候添加的后置处理器的呢?代码很深,不去看了,以后再说。

  • 为什么最后还需要加1?

这个和我们将要分析的第三行代码相关

  1. 3.添加一个BeanPostProcessorChecker,主要用于日志记录

我们看下BeanPostProcessorChecker这个类的源码:

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_ci_11

这段代码主要关注两个方法:

  • isInfrastructureBean,这个方法主要检查当前处理的bean是否是一个Spring自身需要创建的bean,而不是程序员所创建的bean(通过@Component,@Configuration等注解或者XML配置等)。
  • postProcessAfterInitialization,我们可以看到这个方法内部只是做了一个判断,只有当前创建的bean不是一个后置处理器并且不是一个Spring自身需要创建的基础的bean,最后还有一个判断this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount,这个其实就是说在创建bean时容器中的后置处理器还没有完全创建完。这个判断也能解释我们上面遗留的一个问题,之所以加1,是为了方便判断,否则还需要进行等号判断
  1. 上面代码标注的4-7就不解释了,只需要注意的就是registerBeanPostProcessors方法中调用了一个addBeanPostProcessor(BeanPostProcessor beanPostProcessor);,我们看下这个方法的执行逻辑:

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_ci_12

  1. 注意下第8点代码,对于没有实现任何排序接口的后置处理器,Spring是不会进行排序操作的,即使你添加了@Order注解也没用。这里只针对Spring Framework。
  2. 第10点代码又添加了一个后置处理器,添加这个后置处理器主要是为了可以检测到所有的事件监听器,我们看下它的代码:

Spring源码分析(八)容器的扩展点(BeanPostProcessor)_生命周期_13

这个后置处理器注意针对事件监听器(Spring中的事件监听器机制以后在说,这里就把它当做Spring中一个特殊的bean)。上面的代码3-5步可能会让人迷惑,实际上我在之前话的执行流程图中的3-10步Spring就已经注册过一次监听器了,在3-10步骤中,其实Spring已经通过String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);这段代码拿到了所有的名字,那么我们思考一个问题,为什么Spring不直接根据这些名字取过滤创建的bean,而要通过一个特点的后置处理器去进行处理呢?比如可以通过下面这种逻辑:

if(listenerBeanNames.contains(beanName)){
   this.applicationContext.addApplicationListener(bean);
}

这是因为有一种特殊的bean,它会由Spring来创建,自身却不在Spring容器中,这种特殊的bean就是嵌套bean。注意这里说的是嵌套bean,不是内不类,是由下面的XML配置的bean:

<bean class="com.dmz.official.service.IndexService" id="indexService">
   <property name="luBanService">
      <bean class="com.dmz.official.service.LuBanService"/>
   </property>
   <property name="dmzService" ref="dmzService"/>
</bean>

在上面的例子中,LuBanService就是一个嵌套的bean。

假设我们上面的LuBanService是一个事件监听器,那么在getBeanNamesForType这个方法执行时,是无法获取到这个bean的名称的。所以Spring专门提供了上述的那个后置处理器,用于处理这种嵌套bean的情况,但是所提供的嵌套bean必须是单例的。

在分析执行时机时,我们先要知道Spring在创建一个bean时要经历哪些阶段,这里其实涉及到bean的生命周期了,在下篇文章我会专门分析Spring的生命周期,这里主要说明后置处理器的执行时机,先进行一些大致的介绍。

总结

这篇文章主要说了Spring中最后一个扩展点BeanPostProcessor,这里只是对BeanPostProcessor中的方法及执行顺序大致的了解,但是目前为止还不知道每个方法具体的执行时机是什么时候,这个问题放在下篇文章中,结合Spring官网的生命周期回调方法的相关内容一起分析。到此为止可以简单总结如下:

  1. BeanPostProcessor,主要用于干预bean的创建过程。
  2. BeanFactoryPostProcessor,主要用于针对容器中 的BeanDefinition
  3. FactoryBean,主要用于将一个对象直接放入到Spring容器中,同时可以封装复杂对象的创建逻辑

标签:BeanPostProcessor,后置,容器,Spring,bean,源码,处理器
From: https://blog.51cto.com/u_15668812/7428135

相关文章

  • Spring Boot中的依赖管理及自动配置
    你真的理解SpringBoot项目中的parent吗?-SpringBoot2教程合集(javaboy.org)【SpringBoot】SpringBoot项目中的依赖管理及自动配置(qq.com)创建一个SpringBoot项目有三种方式,如在线创建、使用IDEA开发工具创建、使用Maven创建,这三种创建方式,无论是哪一种,创建成功后,pom.......
  • SpringBoot 如何实现文件上传和下载
    当今Web应用程序通常需要支持文件上传和下载功能,SpringBoot提供了简单且易于使用的方式来实现这些功能。在本篇文章中,我们将介绍SpringBoot如何实现文件上传和下载,同时提供相应的代码示例。 文件上传SpringBoot提供了Multipart文件上传的支持。Multipart是HTTP协议中的一种......
  • 3.SpringCloud理解
    SpringCloud是一个用于构建分布式系统的开发工具集合,它基于SpringBoot提供了一套简化的微服务架构开发组件。SpringCloud提供了多个模块,包括服务注册与发现、配置管理、负载均衡、断路器、网关等,这些模块可以帮助开发者快速构建和部署分布式系统。服务注册与发现:通过使......
  • 实现读写分离SpringBoot+MyBatis+Druid
    实现读写分离SpringBoot+MyBatis+Druid1.读写分离概念理解读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做。因此,一般来讲,读写分离有两种实现方式。第一种是依靠中间件(比如:MyCat),也就是说应用程......
  • 在springboot项目种引入element组件
    1、保证vue的版本在3以上2、Win+R--打开命令行窗口(cmd)输入下面的命令,打开图形化界面:vueui3、打开我们创建的vue项目选择路径即可自主导入项目;4、安装element-ui的插件依赖5、查看项目中是否存在ok!......
  • 使用Java和Spring构建RESTful API
    Spring框架简介Spring是一个开源的Java应用程序框架,广泛用于构建企业级应用程序和RESTfulAPI。它提供了丰富的功能集,包括依赖注入、AOP(面向切面编程)、事务管理、Web开发和安全性等。以下是一些关键Spring模块:SpringCore:提供了核心功能,包括依赖注入和Bean管理。SpringBoot:简化了......
  • spring注解
    目录跨域@CrossOrigin跨域@CrossOrigin1:可加在类和方法上2:更推荐用过滤器......
  • spring boot中使用spring-data-jpa
    springbootPOM文件中加入依赖:<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>application.yml配置:spring:datasource:url:jdbc:oracle......
  • springcloud学习笔记
    一、 微服务注册中心Eureka1. Eureka介绍  SpringCloud Eureka是微服务的注册中心,可以管理数据提供者和消费者  说明:    ① Eureka Server是服务端,负责管理各个微服务的注册和发现    ② 需要在Eureka Client中添加响应Eureka配置或代码,微服务启动时就会找到......
  • springcloud学习笔记
    一、 微服务注册中心Eureka1. Eureka介绍  SpringCloud Eureka是微服务的注册中心,可以管理数据提供者和消费者  说明:    ① Eureka Server是服务端,负责管理各个微服务的注册和发现    ② 需要在Eureka Client中添加响应Eureka配置或代码,微服务启动时就会找到......