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

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)

时间:2023-09-12 17:32:16浏览次数:62  
标签:Spring BeanFactoryPostProcessor bean 源码 BeanDefinitionRegistryPostProcessor post

之前的文章我写了BeanDefinition的基本概念和合并,其中很对次提到了容器的扩展点,这篇文章就写这方面的知识。这部分的内容主要涉及到官网的1.8小节。按照官网介绍来说,容器的扩展点可以分为三类,BeanPostProcessor,BeanFactoryPostProcessor以及FactoryBean。本文主要讲BeanFactoryPostProcessor,对应官网的1.8.2小节

总览:

先看官网怎么说:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_官网

从上面这段话可以总结如下几点:

  1. BeanFactoryPostProcessor可以对bean配置元数据进行操作。也就是说Spring容器运行BeanFactoryPostProcessor读取指定bean的配置元数据,并可以在bean被实例化之前修改它。这里说的配置元数据其实就是我们之前讲的BeanDefinition。
  2. 我们可以配置多个BeanFactoryPostProcessor,并且只有我们配置的BeanFactoryPostProcessor同时实现了Ordered接口的话,我们还可以控制这些BeanFactoryPostProcessor的执行顺序

接下来,我们通过demo来感受下BeanFactoryPostProcessor的作用

例子:

这里以官网上的demo为例:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>


<bean id="dataSource" destroy-method="close"
     class="org.apache.commons.dbcp.BasicDataSource">
   <property name="driverClassName" value="${jdbc.driverClassName}"/>
   <property name="url" value="${jdbc.url}"/>
   <property name="username" value="${jdbc.username}"/>
   <property name="password" value="${jdbc.password}"/>
</bean>

实际值来自标准Java Properties格式的另一个文件:

# jdbc.properties
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

在上面的例子中,我们配置了一个PropertyPlaceholderConfigurer,为了方便理解,我们先分析下这个类,其UML类图如下:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_执行流程_02

  • Ordered用于决定执行顺序
  • PriorityOrdered这个接口直接继承了Ordered接口,并且没有做任何扩展,知识作为一个标记接口,也用于决定BeanFactoryPostProcessor的执行顺序。在后文源码分析时能看到它的作用
  • Aware相关的接口以后我在接受Bean的生命周期回调时再同一分析
  • FunctionalInterface是Java8新增的一个接口,也只是起一个标记的作用,标记该接口是一个函数式接口
  • PropertiesLoaderSupport这个类主要包含定义了属性的加载方法,包含的属性如下:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_执行流程_03

  • PropertyResourceConfigurer这个类主要可以对读取到的属性进行一些转换
  • PlaceholderConfigurerSupport主要负责对占位符进行解析。其中几个属性如下:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_执行流程_04

  • PropertyPlaceholderConfigurer继承了上面这些类的所有功能,同时可以配置属性的解析顺序:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_官网_05

对这个类有一些了解后,我们回到之前的例子中,为什么在jdbc.properties文件配置的属性值会被应用到BasicDataSource这个Bean呢。我画个图:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_官网_06

这个流程图就如上图,可以看到我们通过PropertyPlaceholderConfigurer这个特殊的BeanFactoryPostProcessor完成了BeanDefinition中属性值中的占位符替换。在BeanDefinition被解析处理后,Bean实例化之前对其进行了更改。

在上图中,创建bean的过程我们暂且不管,还有一个问题需要弄清楚,Spring是如何扫描并解析成BeanDefinition呢?这里就不得不提接下来需要分析的接口:BeanDefinitionRegistryPostProcessor

org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor:

先看这个接口的UML类图:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_bc_07

从上图可以得出两个结论:

  1. BeanDefinitionRegistryPostProcessor直接继承了BeanFactoryPostProcessor,所以它也是一个bean工厂的后置处理器
  2. Spring只提供了一个内置的BeanDefinitionRegistryPostProcessor的实现类,这个类就是ConfigurationClassPostProcessor,实际上我们上面说的扫描解析成BeanDefinition的过程就是这个类完成的

我们来看下这个接口定义

BeanDefinitionRegistryPostProcessor:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_官网_08

BeanFactoryPostProcessor:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_bc_09

相比于正常的BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor多提供了一个方法,那么多提供的这个方法有什么用呢,会在什么时候执行呢,先说结论:

这个方法的作用也是为了扩展,相比于BeanFactoryPostProcessor的postProcessBeanFactory方法,这个方法的执行时机会更靠前,Spring自身利用这个特性完成了BeanDefinition的扫描注解。我们对Spring进行扩展时,也可以利用这个特性来完成扫描功能。比如最新版的mybatis就是这么做的。关于mybatis和Spring的整合,我打算在写完Spring的扫描以及容器的扩展点这一系列文章后单独用一篇文章进行分析。

接下来我们直接分析其源码,验证上面的结论。

执行流程源码分析:

在分析源码前,我们看看下面这个图,方便大家对Spring的执行流程有个大概的了解:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_执行流程_10

上图表示的是AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class)的执行流程。我们这次分析的代码主要是其中3-5-1流程。代码比较长,拆分为两步:

BeanDefinitionRegistryPostProcessor执行流程:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_bc_11

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_执行流程_12

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_官网_13

BeanFactoryPostProcessor执行流程:

......承接上半部分代码......

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_官网_14

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_执行流程_15

通过源码分析,我们可以将整个Bean工厂的后置处理器的执行流程总结如下:

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_执行流程_16

首先,要明白一点,上图分为左右两个部分,代表的不是两个接口,而是两个方法

  • 一个是BeanDefinitionRegistryPostProcessor特有的postProcessBeanDefinitionRegistry方法
  • 另一个是BeanFactoryPostProcessor的postProcessBeanFactory方法

这里我们以方法为维度区分更好说明问题,postProcessBeanDefinitionRegistry方法的执行时机早于postProcessBeanFactory。并且他们按照上图从左至右的顺序执行。

另外在上面进行代码分析的时候有一个问题,当在执行postProcessBeanDefinitionRegistry方法时,Spring采用了循环的方式,不断的查找是否有新增的BeanDefinitionRegistryPostProcessor,就是下面这段代码:

boolean reiterate = true;
            while (reiterate) {
                reiterate = false;
                postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
                for (String ppName : postProcessorNames) {
                    if (!processedBeans.contains(ppName)) {
                        currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                        processedBeans.add(ppName);
                        reiterate = true;
                    }
                }
                sortPostProcessors(currentRegistryProcessors, beanFactory);
                registryProcessors.addAll(currentRegistryProcessors);
                invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
                currentRegistryProcessors.clear();
            }

但是在执行postProcessBeanFactory并没有进行类似的查找,这是为什么呢?

我自认为主要是Spring在设计时postProcessBeanFactory这个方法不是用于重新注册一个Bean的,而是修改,我们可以看下这个方法上的争端Java doc

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_bc_17

其中最重要的一段话:All bean definitions will have been loaded,所有的BeanDefinition都已经被加载了。

再对比下postProcessBeanDefinitionRegistry这个方法上的Java doc

Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)_执行流程_18

注意这段话:This allows for adding further bean definitions before the next post-processing phase kicks in.运行我们在下一个后置处理器执行前添加更多的BeanDefinition

使用过程的几个问题:

  1. 可以不可以在BeanFactoryPostProcessor去创建一个bean,这样有什么问题?

从技术上来说是可以的,但是正常情况下我们不该这么做,可能会存在该执行的bean工厂后置处理器没有被应用到这个bean上

  1. BeanFactoryPostProcessor可以被配置为懒加载吗?

不能,即使配置了也不会生效,我们将bean工厂后置处理器配置为懒加载这个行为本身就没任何意义

总结:

在这篇文章中,我们最需要了解及掌握的就是BeanFactoryPostProcessor执行的顺序,总结:

  • 先执行直接实现了BeanDefinitionRegistryPostProcessor接口的后置处理器,所有实现了BeanDefinitionRegistryPostProcessor接口的类有两个方法,一个是特有的postProcessBeanDefinitionRegistry方法,一个是继承自父接口的postProcessBeanFactory

postProcessBeanDefinitionRegistry方法早于postProcessBeanFactory

方法执行,对于postProcessBeanDefinitionRegistry的执行顺序又遵循如下原子:

a. 先执行实现了PriorityOrdered接口类中的postProcessBeanDefinitionRegistry方法

b. 再执行实现了Ordered接口类中的postProcessBeanDefinitionRegistry方法

c. 最后执行没有实现上面两个接口类中的postProcessBeanDefinitionRegistry方法

执行完所有的postProcessBeanDefinitionRegistry方法后,再次执行实现了

BeanDefinitionRegistryPostProcessor接口类中的postProcessBeanDefinitionRegistry方法

  • 再执行直接实现了BeanFactoryPostProcessor接口的后置处理器

a. 先执行实现了PriorityOrdered接口类中的postProcessBeanFactory方法

b. 再执行实现了Ordered接口类中的postProcessBeanFactory方法

c. 最后执行没有实现上面两个接口类中的postProcessBeanFactory方法

标签:Spring,BeanFactoryPostProcessor,bean,源码,BeanDefinitionRegistryPostProcessor,post
From: https://blog.51cto.com/u_15668812/7446909

相关文章

  • springboot中的文件上传与下载
    首先回忆一下springmvc中的文件上传1)引入文件上传相关jar包,commons-io、commons-fileupload2)文件上传表单提交方式必须为post3)要求表单的enctype属性必须为:multipart/form-data4)后台接收文件时,使用multipartFile变量与前端name属性值保持一致5)在springmvc的配置文件中必须......
  • springboot单元测试
    参考文章:SpringBoot单元测试详解_springboottest单元测试_三分恶的博客-CSDN博客JUnit4(三)高级之assertThat和Matchers(匹配器)_FXBStudy的博客-CSDN博客 一:junit测试当你的单元测试代码不需要用到SpringBoot功能,而只是一个简单的测试时,你可以直接编写你的Junit测试......
  • 【玩转鲲鹏 DevKit系列】如何快速迁移无源码应用?
    本文分享自华为云社区《【玩转鲲鹏DevKit系列】如何快速迁移无源码应用?》,作者:华为云社区精选。为了帮助广大用户和开发者快速将无源码应用从x86迁移到鲲鹏,鲲鹏DevKit提供了动态二进制翻译工具ExaGear,它能在运行时将x86二进制指令翻译成鲲鹏二进制指令,使得大部分x86应用无需......
  • SpringCloud:Feign实现微服务之间相互请求
    上篇文章说了通过RestTemplate实现微服务之间访问:https://blog.csdn.net/Ber_Bai/article/details/125460941,这篇文章将通过Feign实现微服务之间访问。代码基于RestTemplate实现微服务之间访问基础上进行修改。......
  • 详解SpringBoot下文件上传与下载的实现
    SpringBoot后台如何实现文件上传下载?最近做的一个项目涉及到文件上传与下载。前端上传采用百度webUploader插件。有关该插件的使用方法还在研究中,日后整理再记录。本文主要介绍SpringBoot后台对文件上传与下载的处理。单文件上传/单文件上传@RequestMapping(value="/uploa......
  • 关于Spring i18n国际化 报错No message found under code * for locale 'zh_CN'.的解
    第一步创建资源文件国际化文件命名格式:基本名称_语言_国家.properties 这里我建了两个配置文件,一个是zh_CN中文的,一个是en_GB英文的,然后在里面随便写点测试文本语句第二步bean.xmlspring配置文件1<?xmlversion="1.0"encoding="UTF-8"?>2<beansxmlns="http:/......
  • 开源即时通讯(IM)项目OpenIM源码部署流程
    由于OpenIM依赖的组件较多,开发者需求不一,导致OpenIM部署一直被人诟病,经过几次迭代优化,包括依赖的组件compose的一键部署,环境变量设置一次,全局生效,以及脚本重构,目前OpenIM部署比较丝滑,特写文章分享给大家。OpenIM是什么OpenIM不是一个独立的聊天产品,它不像telegram、S......
  • springBoot spring6 无法加载 thymeleaf的,在html页面中无法智能感知 th:这些
    网上所有的坑我都试过了,还是无法解决问题,@ControllerpublicclassSellController{@RequestMapping("/test01")/*@ResponseBody*/publicStringindex(){return"test01";}}”test01“下面永远是波浪线,无论把网上查出来的解决问题的方案我......
  • docker部署springboot+vue项目环境安装及部署流程
    后端项目打jar包修改及配置项1、修改配置文件application-prod.yml中的mysql配置和redis配置2、切换Maven为生产模式3、Maven打包4、拿到打包后的jar 包5、拿到jar包之后在Linux中使用nohupjava-jarjeecg-system-start-3.5.0.jar>catalina.out2>&1&命令即可启动项......
  • Spring Boot - Parameter 0 of constructor in com.example.iocdi.controller.UserCon
    问题描述如上图所示,在加入了@MapperScan注解之后就出现了这个问题。file:[src/java/config/MybatisConfig.java]@Configuration@MapperScan("com.example.iocdi")publicclassMybatisConfig{}因为这个@MapperScan注解扫描了com.example.iocdi下面所有的包,而U......