聊一聊 BeanPostProcessor 不生效
关于 BeanPostProcessor 各位一定不陌生,在 SpringBoot 源码系列-Bean 的生命周期与扩展 这篇文章中,我有介绍过 bean 的创建流程及相关扩展,就有提到 BeanPostProcessor,包括它的作用时机及如何使用,这篇文章提到的这种属于比较常规的流程,因此在绝大多数场景下,都是符合我们认知的。但是最近在排查一个问题时,发现在某些场景下,BeanPostProcessor 不生效了…
问题描述
代码详见:extention-FactoryBean; clone 之后可以直接运行 DemoApplication 即可,可以观察到 控制台不输出 GlmapperBeanPostProcessor 里面 print out 的字符串。
运行代码,即可观察到具体的执行现场;代码里除了 BeanPostProcessor
之外,另外一个是 FactoryBean
,也就是本篇所要聊的重点:FactoryBean getObjectType
为 null
时导致 bean
提前初始化,从而使得作用与目标 bean
的 BeanPostProcessors
都失效了。
下面将基于这个问题,展开进行分析。
bean 生命周期
先来看下 ApplicationContext
和 bean
生命周期(仅列出部分关键流程):
从流程中可以看到:BeanPostProcessor
的注册是在 ApplicationContext
生命周期中完成的,故而当 bean
创建时,如果相应拦截器 BeanPostProcessor
还没有注册,那么其就不会起作用,这个可能有以下两种原因:
- 1、
bean
本身是一个BeanPostProcessor
,且实现了PriorityOrdered
或者Ordered
接口 - 2、
bean
由于某种原因,被提前初始化了,初始化的时候相应拦截器BeanPostProcessor
还没有注册
关于第一个其实很好理解,不再赘述,本篇主要基于第二个原因进行说明。
bean 由于某种原因,被提前初始化了,初始化的时候相应拦截器 BeanPostProcessor 还没有注册
bean
被提前初始化的情况就比较多了,归纳下来都能符合同一个规律:在 创建所有 non-lazy-init bean
这一步之前,也即在创建 BeanFactoryPostProcessor
或者 BeanPostProcessor
的过程中,引发了 bean
的创建,导致其被提前初始化,大体可以分为两种情形:
- 用户自定义的
BeanFactoryPostProcessor
或者BeanPostProcessor
中会通过构造函数、属性注入等方式引用到目标bean
导致其被提前创建 - 在上述过程中由于
Spring
自身对FactoryBean
的typeCheck
(类型检测) 机制导致目标bean
被提前创建
对于第一种情形,比较简单,这个通常是用户的配置导致的,比如我的 TestBeanFactoryPostProcessor
中通过属性注入了目标 bean
导致了其被提前创建,最终拦截器失效(如果去掉相应 TestBeanFactoryPostProcessor
配置,可以看到拦截器是能够成功的 )。
简单代码如下,作用在
TestFacade
上的BeanFactoryPostProcessor
可能会由于TestFacade
的提前被创建而失效
1 | public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor { |
如何找到
bean
被提前初始化的时机呢?可以在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
打一个条件断点,通过beanName
进行匹配,然后顺着debug
堆栈往回找,就能够看到是在哪里导致了bean
被提前创建。
对于第二种情形,其实也是通过上述方法先找到被提前创建的源头,只不过这种情形更加隐晦,也更加复杂,这里我们单独在下面的部分中来分析。
关于 isTypeMatch
从 Spring 2.x 版本开始,BeanFactory
中就已经有 isTypeMatch
这个方法了
1 | /** |
从方法注释可以简单了解到,isTypeMatch
的作用就是:判断 JavaBean
是否匹配指定的类型。他包括两个参数:
name:容器中定义的
JavaBean
的名称。typeToMatch:要匹配的目标类型。
回到案例,我们需要关注的是isTypeMatch
和我们前面提到的FactoryBean getObjectType
为null
时导致bean
提前初始化,从而使得作用与目标bean
的BeanPostProcessors
都失效了。有什么关系呢?这里有两个比较关键的信息:1、FactoryBean getObjectType 为 null
2、目标 bean 的 BeanPostProcessors 都失效了
其实大概能够猜到的是,FactoryBean getObjectType
为 null
时,导致了 当前 bean
被提前初始化,而此时 bean
的 BeanPostProcessors
还没有被注册到当前 bean
,从而导致了目标 bean
的 BeanPostProcessors
都失效。 这个也是本篇的结论,但是还是需要来看看具体原因的细节是什么样的。
我们知道,在 Spring
中,当进行 byType
(除了用户主动配置 byType
注入以外,使用 @autowired
以及 @Bean
中的方法参数时等都使用的是 byType
注入) 注入时,会通过 org.springframework.beans.factory.ListableBeanFactory#getBeanNamesForType(java.lang.Class<?>, boolean, boolean)
来寻找相应类型的 bean
。
针对 FactoryBean
而言,当判断其类型时,会先创建一个简单的(非完整的,仅仅是调用构造函数) bean
,调用其 getObjectType()
,如果发现返回为 null
,那么就会再创造完整的 bean
,然后再通过 getObjectType()
获取类型进行匹配。
详细分析
基于上面提到的点,结合本案例,来 debug
看下 FactoryBean
typeCheck
(类型检测) 机制导致的 BeanPostProcessor
不生效的原因。
这里主要还是看下 isTypeMatch 方法执行是如何触发 bean 提前初始化的。
isTypeMatch 方法
1 |
|
getTypeForFactoryBean 方法
这个步骤会向尝试从 FactoryBean
的 getObjectType
方法去获取类型,如果拿不到,则调用父类的进行初始化 bean
操作
1 | // 省略 其他... |
AbstractBeanFactory#getTypeForFactoryBean
调用父类的 getTypeForFactoryBean
方法,执行 bean
的初始化
1 |
|
在 doGetBean
中执行链路中,会在 initializeBean
时给当前 bean
注册 BeanPostProcessor
,(applyBeanPostProcessorsBeforeInitialization
方法中) ,这里可以比较清晰的看到 BeanPostProcessor
没有作用于 目标 bean
的。
doGetBean -> createBean -> initializeBean -> applyBeanPostProcessorsBeforeInitialization
小结
在本篇的案例中,其实比较明显的可以看到测试工程中 GlmapperFactoryBean
的 getObjectType
返回是为 null
的,也正是因为这个原因导致了 BeanPostProcessor
失效。那么如何在实际的开发过程中来规避呢?
- 1、FactoryBean 的 getObjectType() 不要返回 null
- 2、定义 BeanPostProcessor 时,需要特别注意 order
- 3、在 创建所有 non-lazy-init bean 之前的 getBeanNamesForType 调用,尽量将 eagerInit 传为 false。
关于第三点,前面提到过 getBeanNamesForType
的调用会触发类型检查,但其实这个方法还有些参数,参考如下:
1 | String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit); |
这里有个很重要的参数 allowEagerInit
,可以看到 spring
的注释中对其有非常详细的解释:
1 | * allowEagerInit whether to initialize lazy-init singletons and |
简单来说这个参数能够控制是否允许 FactoryBean
的提前创建,如果是 false
,那么也不会引发上述的 类型检测 。可以看到在 Spring
中在获取 BeanFactoryPostProcessor
以及 BeanPostProcessor
时,也都是传入 false
的。
1 | String[] postProcessorNames = |
当然在一些 @Bean
的方法参数注入、@Autowire
注入等场景下,这个默认都是 true
的,无法改变;但针对平时编码过程中,如果是在比较早期的调用中,可根据情况,尽量传入 false
。
聊一聊 BeanPostProcessor 不生效
http://www.glmapper.com/2020/06/21/spring/spring-series-factory-bean-and-post-processor/