glmapper

聊一聊 BeanPostProcessor 不生效

字数统计: 2.9k阅读时长: 12 min
2020/06/21 Share

关于 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
2
3
4
5
6
7
8
9
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

@@Autowired
private TestFacade testFacade;

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// ...
}

如何找到 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Check whether the bean with the given name matches the specified type.
* More specifically, check whether a {@link #getBean} call for the given name
* would return an object that is assignable to the specified target type.
* <p>Translates aliases back to the corresponding canonical bean name.
* Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to query
* @param typeToMatch the type to match against (as a {@code Class})
* @return {@code true} if the bean type matches,
* {@code false} if it doesn't match or cannot be determined yet
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
* @since 2.0.1
* @see #getBean
* @see #getType
*/
boolean 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 都失效了

其实大概能够猜到的是,actoryBean 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
@Override
public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);

// Check manually registered singletons.
Object beanInstance = getSingleton(beanName, false);
// 常规情况下,这里 beanInstance 是不为 null 的,但是对于提前加载的 beanInstance == null
if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
// 判断类型是不是 FactoryBean
if (beanInstance instanceof FactoryBean) {
// 返回给定名称是否为工厂解除引用(以工厂解除引用前缀开始)。 &xxxx
if (!BeanFactoryUtils.isFactoryDereference(name)) {
// 这里拿 FactoryBean#getObjectType
Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);
return (type != null && typeToMatch.isAssignableFrom(type));
}
else {
// 实例类型是否匹配
return typeToMatch.isInstance(beanInstance);
}
}

// 处理泛型和代理
else if (!BeanFactoryUtils.isFactoryDereference(name)) {
if (typeToMatch.isInstance(beanInstance)) {
// 直接匹配暴露实例?
return true;
}
else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) {
// 泛型可能只匹配目标类,而不匹配代理…
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
Class<?> targetType = mbd.getTargetType();
if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance) &&
typeToMatch.isAssignableFrom(targetType)) {
// 还要检查原始类匹配,确保它在代理上暴露。
Class<?> classToMatch = typeToMatch.resolve();
return (classToMatch == null || classToMatch.isInstance(beanInstance));
}
}
}
return false;
}

// 当前 beanName 的 bean 没有被注册过
else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
// null instance registered
return false;
}

// 没有找到单例实例->检查bean定义。
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 在这个 factory 中没有找到 bean definition -> 委托 parent。
return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch);
}

// 检索相应的 bean 定义。
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

Class<?> classToMatch = typeToMatch.resolve();
if (classToMatch == null) {
classToMatch = FactoryBean.class;
}
Class<?>[] typesToMatch = (FactoryBean.class == classToMatch ?
new Class<?>[] {classToMatch} : new Class<?>[] {FactoryBean.class, classToMatch});

// Check decorated bean definition, if any: We assume it'll be easier
// to determine the decorated bean's type than the proxy's type.
// 检查修饰 bean definition(如果有的话):我们假设确定修饰 bean 的类型比确定代理的类型更容易。
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) {
RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
// 预测指定bean的最终bean类型(已处理bean实例的)。由{@link #getType}和{@link #isTypeMatch}调用。不需要专门处理factorybean,因为它只应该操作原始bean类型。
// 这个实现过于简单,因为它不能处理工厂方法和实例化 awarebeanpostprocessors。对于标准bean,它只能正确地预测bean类型。要在子类中重写,应用更复杂的类型检测。
Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch);
if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
return typeToMatch.isAssignableFrom(targetClass);
}
}
// 推断出 beanType
Class<?> beanType = predictBeanType(beanName, mbd, typesToMatch);
if (beanType == null) {
return false;
}

// 检查 bean class 是否是 FactoryBean 类型。本案例就是在这被处理到 返回 false 的
if (FactoryBean.class.isAssignableFrom(beanType)) {
if (!BeanFactoryUtils.isFactoryDereference(name) && beanInstance == null) {
// 如果它是FactoryBean,我们希望看到它创建了什么(getObject),而不是工厂类。
beanType = getTypeForFactoryBean(beanName, mbd);
if (beanType == null) {
return false;
}
}
}
// 省略 ........

}

getTypeForFactoryBean 方法

这个步骤会向尝试从 FactoryBean 的 getObjectType 方法去获取类型,如果拿不到,则调用父类的进行初始化 bean 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 省略 其他...
if (fb != null) {
// 尝试从实例的这个早期阶段获取 FactoryBean 的对象类型。这里调用的就是 FactoryBean#getObjectType 方法
Class<?> result = getTypeForFactoryBean(fb);
// 本案例中这里返回的是 null, 所以会走到 else
if (result != null) {
return result;
}
else {
// 这里的意思就是没有通过 FactoryBean#getObjectType 快速获取到类型
// 将执行实例当前实例,然后再获取
return super.getTypeForFactoryBean(beanName, mbd);
}
}
// 省略 其他...

AbstractBeanFactory#getTypeForFactoryBean

调用父类的 getTypeForFactoryBean 方法,执行 bean 的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Nullable
protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
if (!mbd.isSingleton()) {
return null;
}
try {
// 这里开始执行 doGetBean,之前的文章里面有提到,bean 实例化的入口就是 getBean 的时候
FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
return getTypeForFactoryBean(factoryBean);
}
catch (BeanCreationException ex) {
// 省略日志打印部分
return null;
}
}

在 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
2
3
4
5
6
@param allowEagerInit whether to initialize lazy-init singletons and

* objects created by FactoryBeans (or by factory methods with a
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references.

简单来说这个参数能够控制是否允许 FactoryBean 的提前创建,如果是 false,那么也不会引发上述的 类型检测 。可以看到在 Spring 中在获取 BeanFactoryPostProcessor 以及 BeanPostProcessor 时,也都是传入 false 的。

1
2
3
4
5
tring[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

当然在一些 @Bean 的方法参数注入、@Autowire 注入等场景下,这个默认都是 true 的,无法改变;但针对平时编码过程中,如果是在比较早期的调用中,可根据情况,尽量传入 false。

原文作者:GuoLei Song

原文链接:http://www.glmapper.com/2020/06/21/spring-series-factory-bean-and-post-processor/

发表日期:June 21st 2020, 9:12:14 pm

更新日期:June 25th 2020, 3:52:56 pm

版权声明:转载请注明出处

CATALOG
  1. 1. 问题描述
  2. 2. bean 生命周期
  3. 3. bean 由于某种原因,被提前初始化了,初始化的时候相应拦截器 BeanPostProcessor 还没有注册
  4. 4. 关于 isTypeMatch
  5. 5. 详细分析
    1. 5.1. isTypeMatch 方法
    2. 5.2. getTypeForFactoryBean 方法
    3. 5.3. AbstractBeanFactory#getTypeForFactoryBean
  6. 6. 小结