Spring进阶篇(8)-BeanPostProcessor的注册时机

JAVA && Spring && SpringBoot2.x — 学习目录

在项目中,我们可以将BeanPostProcessor注册到IOC容器中,有什么注意事项呢?

1. 问题起源:

在shiro中,若配置LifecycleBeanPostProcessor后,可能会导致一些类不能支持事务等代理功能。

@Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

注:LifecycleBeanPostProcessor是Bean的后置处理器,shiro去控制Bean的生命周期。但不推荐在配置中加入上述配置,因为Bean的生命周期一般由Spring去控制。不推荐交由shiro进行控制!

事务失效场景复现:

@Configuration
public class ShiroConfig {
    //配置过滤器链
    @Bean("shiroFilterBean")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager,JwtAuthcFilter jwtAuthcFilter,JwtPermissionFilter jwtPermissionFilter,Ini.Section section) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        ...
        return shiroFilterFactoryBean;
    }
    //配置shiro生命周期后处理器
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}

之后,我们发现ShiroFilterFactoryBean依赖的Bean,事务等功能全部失效(即依赖的Bean不是代理对象)。

2. 问题分析

BeanPostProcessor本质上也是一个Bean对象,但是它的初始化时机会早于普通Bean对象。实际上Bean的生命周期大体可分为:

  1. new 对象(反射)。
  2. 属性依赖注入。
  3. 调用生命周期回调方法。
  4. 完成AOP代理。

而实际上无论是属性的依赖注入还是完成AOP代理实际上均是在后置处理器BeanPostProcessor中进行统一处理的。

若普通Bean初始化时,BeanPostProcessor没有全部初始化,那么可能会造成普通Bean对象功能上的缺失。在Spring启动过程中,会抛出如下警告:

//没有得到所有后置处理器的处理,例如缺失了自动代理后置处理器处理。
Bean 'accountImpl' of type [com.tellme.Impl.AccountImpl] is not eligible for getting 
processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

但是有些同学会问,在自定义BeanPostProcessor时依赖普通Bean,可能会造成自动代理失效。但"问题起源"中仅仅引入了shiro定义的LifecycleBeanPostProcessor,为什么还会出现上述问题?

3. 问题原因

在Spring源码中,注册BeanPostProcessor时,会调用getBean()方法获取Bean对象。

//源码位置:org.springframework.context.support.PostProcessorRegistrationDelegate#registerBeanPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, org.springframework.context.support.AbstractApplicationContext)
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);

在获取Bean对象时,遍历所有的BeanDefinition,来获取对应的Bean。若容器中存在FactoryBean时,会检验FactoryBean创建的Bean类型,而不是FactoryBean的类型。故检测FactoryBean类型时,可能会将普通Bean实例化。

//核心源码:
1. org.springframework.beans.factory.support.DefaultListableBeanFactory#doGetBeanNamesForType
2. org.springframework.beans.factory.support.AbstractBeanFactory#isTypeMatch(java.lang.String, org.springframework.core.ResolvableType)

LifecycleBeanPostProcessor是在@Configuration中进行注册的。并且注册时@Configuration容器中也包含了一个FactoryBean对象(ShiroFilterFactoryBean)

这样会导致注册Ordered级别的BeanPostProcessor调用getBean()方法时,会将ShiroFilterFactoryBean以及其依赖的属性进行初始化,失去同级别的后置处理器的处理。

总结:BeanPostProcessor@Configuration容器过早的初始化。于是在注册后置处理器,会调用getBean()导致FactoryBean过早被初始化。

4. 问题复现

自定义一个PriorityOrdered级别的后置处理器,启动项目,查看Bean的初始化时机。

  1. UserServiceBeanFactoryBean并使用@Service注册到容器;
@Service
public class UserServiceBean implements FactoryBean {
    @Autowired
    private AcService acService;  //包含一个普通Bean,查看给普通Bean的初始化时机
    public UserServiceBean() {
        System.out.println("UserServiceBean 的构造方法..");
    }
    @Override
    public Object getObject() throws Exception {
        return "";
    }
    @Override
    public Class getObjectType() {
        return Object.class;
    }
}
  1. aConfig是一个@Configuration类,其中包含了
    2.1 PersonServiceBean一个FactoryBean;
    2.2 MyBeanPostProcessor是一个PriorityOrdered级别的后置处理器;
    2.3 AcService一个普通的Service类;
@Configuration
public class aConfig {
    public aConfig() {
        System.out.println("aConfig 构造函数...");
    }
    @Bean
    public PersonServiceBean personService(IAccount account) {
        PersonServiceBean personServiceBean = new PersonServiceBean();
        personServiceBean.setAccount(account);
        return personServiceBean;
    }
    @Bean
    public MyBeanPostProcessor myBeanPostProcessor() {
        return new MyBeanPostProcessor();
    }
}
//后置处理器
public class MyBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
    public MyBeanPostProcessor() {
        System.out.println("MyBeanPostProcessor 初始化...");
    }
    //低优先级
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
//FactoryBean类
public class PersonServiceBean implements FactoryBean {
    private IAccount account;
    public IAccount getAccount() {
        return account;
    }
    public PersonServiceBean() {
        System.out.println("PersonServiceBean 构造方法...");
    }
    public void setAccount(IAccount account) {
        this.account = account;
    }
    @Override
    public Object getObject() throws Exception {
        return "";
    }
    @Override
    public Class getObjectType() {
        return Object.class;
    }
}
  1. bConfig是一个@Configuration类,包含了一个SomeServiceBean
//FactoryBean类
public class SomeServiceBean implements FactoryBean {
    public SomeServiceBean() {
        System.out.println("SomeServiceBean 的构造方法");
    }
    @Override
    public Object getObject() throws Exception {
        return "";
    }
    @Override
    public Class getObjectType() {
        return Object.class;
    }
}

执行结果

UserServiceBean 的构造方法..   (使用@Service注解FactoryBean)
aConfig 构造函数...            (带有后置处理器的@Configuration容器,容器先初始化,才会初始化后置处理器。)
MyBeanPostProcessor 初始化...  (PriorityOrdered级别的后置处理器)
AccountImpl 的构造方法...      (FactoryBean所依赖的普通Bean,accountImpl是方法参数,先初始化它,才能进行FactoryBean的构造)
PersonServiceBean 构造方法...  (FactoryBean的构造方法。)

bConfig 构造方法...            (普通的@Configuration容器)
SomeServiceBean 的构造方法     (普通容器中的FactoryBean方法)
AcService 的构造方法...        (带有@Service的普通Bean)

可以看到,MyBeanPostProcessor导致了aConfig过早的初始化。在注册Ordered级别的BeanPostProcessor时,遍历所有的BeanDefinition对象,导致aConfigPersonServiceBean被初始化。而PersonServiceBean在初始化之前,要先初始化方法参数,即将AccountImpl提前初始化。

而实际上,若是@Service注册的FactoryBean,其依赖的属性AcService也不会过早的被初始化。

5. 归纳总结

我们可以先看下SpringBoot2.x如何去注册后置处理器的。

  1. 注册@Validated后置处理器

源码:org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration

public class ValidationAutoConfiguration {
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @ConditionalOnMissingBean(Validator.class)
    public static LocalValidatorFactoryBean defaultValidator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
        factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
        return factoryBean;
    }
    @Bean
    @ConditionalOnMissingBean
    public static MethodValidationPostProcessor methodValidationPostProcessor(
            Environment environment, @Lazy Validator validator) {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        boolean proxyTargetClass = environment
                .getProperty("spring.aop.proxy-target-class", Boolean.class, true);
        processor.setProxyTargetClass(proxyTargetClass);
        processor.setValidator(validator);
        return processor;
    }
}

MethodValidationPostProcessor和其他Bean在一个容器中,那么注册时使用static方法。

  1. 注册@Async 后置处理器:

源码:org.springframework.scheduling.annotation.ProxyAsyncConfiguration

public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {

    @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
        Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
        AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
        bpp.configure(this.executor, this.exceptionHandler);
        Class customAsyncAnnotation = this.enableAsync.getClass("annotation");
        if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
            bpp.setAsyncAnnotationType(customAsyncAnnotation);
        }
        bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
        bpp.setOrder(this.enableAsync.getNumber("order"));
        return bpp;
    }
}

将后置处理器单独放在了一个容器中。

总结:

BeanPostProcessor的注册需要注意时机,防止将@Configuration容器中过早的初始化,造成一些Bug。根据源码来讲,一般推荐两种方式:

  1. BeanPostProcessor单独的放在一个@Configuration容器中;
  2. 若与其他Bean共用一个@Configuration容器,推荐使用static方法注册后置处理器

你可能感兴趣的:(Spring进阶篇(8)-BeanPostProcessor的注册时机)