转载

聊一聊-不生效的 BeanPostProcessor

原文链接: www.glmapper.com/2020/06/21/…

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

从流程中可以看到: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 的提前被创建而失效

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 这个方法了

/**
* 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 不生效 的原因。

聊一聊-不生效的 BeanPostProcessor

这里主要还是看下 isTypeMatch 方法执行是如何触发 bean 提前初始化的。

isTypeMatch 方法

@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 操作

// 省略 其他...
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 的初始化

@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
聊一聊-不生效的 BeanPostProcessor

小结

在本篇的案例中,其实比较明显的可以看到测试工程中 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 的注释中对其有非常详细的解释:

@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 的。

tring[] postProcessorNames =
        beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

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

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

原文  https://juejin.im/post/5ef5b0096fb9a07e87670382
正文到此结束
Loading...