深入研究 BeanFactoryPostProcessor
D瓜哥在 Spring 扩展点概览及实践 中概要性地介绍了一下 Spring 的核心扩展点。里面也提到了 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor,但仅仅提了一句,没有深入研究。在 Spring 扩展点实践:整合 MyBATIS 中,由于 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,也只是简单介绍了一些作用,又一次没有深入研究。
最近,在开发一个插件时,遇到了一个问题:利用 BeanFactoryPostProcessor 对一些特定 BeanDefinition 设置属性,但生成的 Bean 却没有相关的属性值。由此,对 BeanFactoryPostProcessor 做了一些研究。记录一下,以备不时之需。
Spring 启动流程简介
在 Spring 启动流程概述 中,D瓜哥对 Spring 的启动流程做了比较详细的介绍。同时画了一张启动流程图,如下:
从该图中可以明显看到,如果需要对 Spring 的 BeanDefinition 做些修改,那么,就需要通过实现 BeanFactoryPostProcessor 接口,来对 Spring 做些扩展。坦白讲,为了上述流程图只展示了一个非常概要性的流程。如果深入一下 invokeBeanFactoryPostProcessors 方法的细节,会发现这又是一番天地。
BeanFactoryPostProcessor 调用详解
D瓜哥把 invokeBeanFactoryPostProcessors 方法的流程图也画了出来,细节如下:
从这张流程图上可以看出 BeanFactoryPostProcessor 的调用过程,比在 Spring 启动流程概述 中介绍的要复杂很多:
首先,执行
BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry方法,顺序如下:关于
BeanDefinitionRegistryPostProcessor的处理流程,D瓜哥在 Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor 中有更详细的描述,不了解的朋友请参考那篇文章的介绍。用户手动添加的
BeanDefinitionRegistryPostProcessor实现类;实现
PriorityOrdered接口的BeanDefinitionRegistryPostProcessor实现类;实现
Ordered接口的BeanDefinitionRegistryPostProcessor实现类;剩余所有的
BeanDefinitionRegistryPostProcessor实现类;而且是双重循环执行,只要发现有新加入的BeanDefinitionRegistryPostProcessor实例,就再循环调用一遍这些新实例。
然后,执行
BeanFactoryPostProcessor#postProcessBeanFactory方法。顺序如下:实现
BeanDefinitionRegistryPostProcessor接口的类;由于BeanDefinitionRegistryPostProcessor接口继承了BeanFactoryPostProcessor接口,所以,一个BeanDefinitionRegistryPostProcessor实例,也是一个BeanFactoryPostProcessor实例。用户手动添加的
BeanFactoryPostProcessor实现类;实现
PriorityOrdered接口的BeanFactoryPostProcessor实现类;实现
Ordered接口的BeanFactoryPostProcessor实现类;剩余所有的
BeanFactoryPostProcessor实现类;(注:这步不排序,其余都需要对其实例进行排序。)
程序执行过程
下面看一个普通项目执行过程中的实际运行情况。请注意看 RecorderBeanFactoryPostProcessor 和 PropertySourcesFactoryPostProcessor 的位置。这也是文章开头提到的不生效问题的关键原因所在。
1.b 实现 PriorityOrdered 接口的 BeanDefinitionRegistryPostProcessor 实现类

该截图对应上面的 1.b 节的内容:执行的是实现 PriorityOrdered 接口的 BeanDefinitionRegistryPostProcessor 实现类的 postProcessBeanDefinitionRegistry 方法。
1.c 实现 Ordered 接口的 BeanDefinitionRegistryPostProcessor 实现类

该截图对应上面的 1.c 节的内容:执行的是实现 Ordered 接口的 BeanDefinitionRegistryPostProcessor 实现类的 postProcessBeanDefinitionRegistry 方法。
1.d 剩余所有的 BeanDefinitionRegistryPostProcessor 实现类

该截图对应上面的 1.d 节的内容:执行的剩余的 BeanDefinitionRegistryPostProcessor 实现类的 postProcessBeanDefinitionRegistry 方法。由于这个过程中,还在不断的向容器注册 BeanDefinition,如果有新加入的 BeanDefinitionRegistryPostProcessor 实现类,就需要再循环一遍去执行一次。所以,这里是双重循环。
2.a 所有实现 BeanDefinitionRegistryPostProcessor 接口的类

该截图对应上面的 2.a 节的内容:执行所有实现 BeanDefinitionRegistryPostProcessor 接口的类的 postProcessBeanFactory 方法。
2.c 实现 PriorityOrdered 接口的 BeanFactoryPostProcessor 实现类

2.d 实现 Ordered 接口的 BeanFactoryPostProcessor 实现类

2.e 剩余所有的 BeanFactoryPostProcessor 实现类

关键 BeanFactoryPostProcessor 实现类的继承关系
从上面 程序执行过程 中可以看到:由于 PropertySourcesFactoryPostProcessor 实现了 PriorityOrdered 接口,而 RecorderBeanFactoryPostProcessor 没有,那么,Spring 执行过程中就会先执行 PropertySourcesFactoryPostProcessor,然后再执行 RecorderBeanFactoryPostProcessor,而 PropertySourcesFactoryPostProcessor 的执行会导致一些 Bean 被初始化,那么再执行 RecorderBeanFactoryPostProcessor 时,所以修改了这些 Bean 对应的 BeanDefinition 中关于 Bean 的定义,但由于实例已经被创建出来了,所以这些新增的属性就无法生效了。解决问题的方法也很简单:让 RecorderBeanFactoryPostProcessor 也实现 PriorityOrdered 接口并给予更高的优先级。来个类图,一目了然:
优化后的执行结果

从该图上可以清楚看到:优化后的 RecorderBeanFactoryPostProcessor 比 PropertySourcesFactoryPostProcessor 可以更早的执行,这样就可以防止部分 Bean 被提前创建出来。
分享两个小技巧
通过上面的分析,想必大家多问题的原因和解决办法了然于胸。最后,再给大家分享两个小技巧。
配置参数
在D瓜哥的场景中,让 RecorderBeanFactoryPostProcessor 也实现 PriorityOrdered 接口并给予更高的优先级,那么,原来在 RecorderBeanFactoryPostProcessor 中使用的 @Value("#{com.diguage.token}") 注解不能正确解析了,直接把整个字符串没有经过占位符解析就完整传递进来了。经过多次尝试发现,可以让 RecorderBeanFactoryPostProcessor 实现 EnvironmentAware 接口,这样就可以获得 Environment 对象,然后从该对象中获取配置参数。
对于配置参数的设置,可以通过 -Dcom.diguage.token=www.diguage.com 的方式,传递给 java 命令,这样在程序中就可以获取该值。
关于 Spring 中占位符的解析和处理,请看: Spring 对占位符的处理(一):XML 中的 Bean 等文章。
打印 Bean 创建日志
Spring 中 Bean 的创建入口在 AbstractAutowireCapableBeanFactory 中。可以将该类的日志级别设置为 TRACE 就可以看到创建日志了。
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n
</pattern>
</encoder>
</appender>
<!-- 创建 Bean 的日志在该类中打印 -->
<logger name="org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory"
level="TRACE"/>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>


