关于 BeanPostProcessor 各位一定不陌生,在 SpringBoot 源码系列-Bean 的生命周期与扩展 (http://www.glmapper.com/2019/12/14/springboot-series-bean-life/) 这篇文章中,有介绍过 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 的提前被创建而失效
1public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
2
3 @@Autowired
4 private TestFacade testFacade;
5
6 @Override
7 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
8 // ...
9 }
如何找到 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/**
2* Check whether the bean with the given name matches the specified type.
3* More specifically, check whether a {@link #getBean} call for the given name
4* would return an object that is assignable to the specified target type.
5* Translates aliases back to the corresponding canonical bean name.
6* Will ask the parent factory if the bean cannot be found in this factory instance.
7* @param name the name of the bean to query
8* @param typeToMatch the type to match against (as a {@code Class})
9* @return {@code true} if the bean type matches,
10* {@code false} if it doesn't match or cannot be determined yet
11* @throws NoSuchBeanDefinitionException if there is no bean with the given name
12* @since 2.0.1
13* @see #getBean
14* @see #getType
15*/
16boolean isTypeMatch(String name, @Nullable Class typeToMatch) throws NoSuchBeanDefinitionException;
从方法注释可以简单了解到,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@Override
2public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
3 String beanName = transformedBeanName(name);
4
5 // Check manually registered singletons.
6 Object beanInstance = getSingleton(beanName, false);
7 // 常规情况下,这里 beanInstance 是不为 null 的,但是对于提前加载的 beanInstance == null
8 if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
9 // 判断类型是不是 FactoryBean
10 if (beanInstance instanceof FactoryBean) {
11 // 返回给定名称是否为工厂解除引用(以工厂解除引用前缀开始)。 &xxxx
12 if (!BeanFactoryUtils.isFactoryDereference(name)) {
13 // 这里拿 FactoryBean#getObjectType
14 Class type = getTypeForFactoryBean((FactoryBean) beanInstance);
15 return (type != null && typeToMatch.isAssignableFrom(type));
16 }
17 else {
18 // 实例类型是否匹配
19 return typeToMatch.isInstance(beanInstance);
20 }
21 }
22
23 // 处理泛型和代理
24 else if (!BeanFactoryUtils.isFactoryDereference(name)) {
25 if (typeToMatch.isInstance(beanInstance)) {
26 // 直接匹配暴露实例?
27 return true;
28 }
29 else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) {
30 // 泛型可能只匹配目标类,而不匹配代理…
31 RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
32 Class targetType = mbd.getTargetType();
33 if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance) &&
34 typeToMatch.isAssignableFrom(targetType)) {
35 // 还要检查原始类匹配,确保它在代理上暴露。
36 Class classToMatch = typeToMatch.resolve();
37 return (classToMatch == null || classToMatch.isInstance(beanInstance));
38 }
39 }
40 }
41 return false;
42 }
43
44 // 当前 beanName 的 bean 没有被注册过
45 else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
46 // null instance registered
47 return false;
48 }
49
50 // 没有找到单例实例->检查bean定义。
51 BeanFactory parentBeanFactory = getParentBeanFactory();
52 if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
53 // 在这个 factory 中没有找到 bean definition -> 委托 parent。
54 return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch);
55 }
56
57 // 检索相应的 bean 定义。
58 RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
59
60 Class classToMatch = typeToMatch.resolve();
61 if (classToMatch == null) {
62 classToMatch = FactoryBean.class;
63 }
64 Class[] typesToMatch = (FactoryBean.class == classToMatch ?
65 new Class[] {classToMatch} : new Class[] {FactoryBean.class, classToMatch});
66
67 // Check decorated bean definition, if any: We assume it'll be easier
68 // to determine the decorated bean's type than the proxy's type.
69 // 检查修饰 bean definition(如果有的话):我们假设确定修饰 bean 的类型比确定代理的类型更容易。
70 BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
71 if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) {
72 RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
73 // 预测指定bean的最终bean类型(已处理bean实例的)。由{@link #getType}和{@link #isTypeMatch}调用。不需要专门处理factorybean,因为它只应该操作原始bean类型。
74 // 这个实现过于简单,因为它不能处理工厂方法和实例化 awarebeanpostprocessors。对于标准bean,它只能正确地预测bean类型。要在子类中重写,应用更复杂的类型检测。
75 Class targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch);
76 if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
77 return typeToMatch.isAssignableFrom(targetClass);
78 }
79 }
80 // 推断出 beanType
81 Class beanType = predictBeanType(beanName, mbd, typesToMatch);
82 if (beanType == null) {
83 return false;
84 }
85
86 // 检查 bean class 是否是 FactoryBean 类型。本案例就是在这被处理到 返回 false 的
87 if (FactoryBean.class.isAssignableFrom(beanType)) {
88 if (!BeanFactoryUtils.isFactoryDereference(name) && beanInstance == null) {
89 // 如果它是FactoryBean,我们希望看到它创建了什么(getObject),而不是工厂类。
90 beanType = getTypeForFactoryBean(beanName, mbd);
91 if (beanType == null) {
92 return false;
93 }
94 }
95 }
96 // 省略 ........
97
98}
getTypeForFactoryBean 方法
这个步骤会先尝试从 FactoryBean 的 getObjectType 方法去获取类型,如果拿不到,则调用父类的进行初始化 bean 操作
1// 省略 其他...
2if (fb != null) {
3 // 尝试从实例的这个早期阶段获取 FactoryBean 的对象类型。这里调用的就是 FactoryBean#getObjectType 方法
4 Class result = getTypeForFactoryBean(fb);
5 // 本案例中这里返回的是 null, 所以会走到 else
6 if (result != null) {
7 return result;
8 }
9 else {
10 // 这里的意思就是没有通过 FactoryBean#getObjectType 快速获取到类型
11 // 将执行实例当前实例,然后再获取
12 return super.getTypeForFactoryBean(beanName, mbd);
13 }
14}
15// 省略 其他...
AbstractBeanFactory#getTypeForFactoryBean
调用父类的 getTypeForFactoryBean 方法,执行 bean 的初始化
1@Nullable
2protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
3 if (!mbd.isSingleton()) {
4 return null;
5 }
6 try {
7 // 这里开始执行 doGetBean,之前的文章里面有提到,bean 实例化的入口就是 getBean 的时候
8 FactoryBean factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
9 return getTypeForFactoryBean(factoryBean);
10 }
11 catch (BeanCreationException ex) {
12 // 省略日志打印部分
13 return null;
14 }
15}
在 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 的调用会触发类型检查,但其实这个方法还有些参数,参考如下:
String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit);
这里有个很重要的参数 allowEagerInit ,可以看到 spring 的注释中对其有非常详细的解释:
1@param allowEagerInit whether to initialize lazy-init singletons and
2
3* objects created by FactoryBeans (or by factory methods with a
4* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
5* eagerly initialized to determine their type: So be aware that passing in "true"
6* for this flag will initialize FactoryBeans and "factory-bean" references.
简单来说这个参数能够控制是否允许 FactoryBean 的提前创建,如果是 false,那么也不会引发上述的 类型检测 。可以看到在 Spring 中在获取 BeanFactoryPostProcessor 以及 BeanPostProcessor 时,也都是传入 false 的。
1tring[] postProcessorNames =
2 beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
3
4String[] postProcessorNames =
5 beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
当然在一些 @Bean 的方法参数注入、@Autowire 注入等场景下,这个默认都是 true 的,无法改变;但针对平时编码过程中,如果是在比较早期的调用中,可根据情况,尽量传入 false。
本文部分内容是经作者允许后发布的
领取专属 10元无门槛券
私享最新 技术干货