首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

聊一聊 BeanPostProcessor 不生效

关于 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。

本文部分内容是经作者允许后发布的

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200625A0EYL800?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券