使用BeanFactoryPostProcessor——这种姿势不要用

使用BeanFactoryPostProcessor这种姿势不要用

前言

在公司内,Spring基本都是首选的IOC框架,Spring也提供了很多扩展点让我们介入到容器的生命周期中,例如BeanFactoryPostProcessor、BeanPostProcessor等。今天就记录下BeanFactoryPostProcessor的一种不正确用法。

约定

实例化:instantiation 初始化:initialization

BeanFactoryPostProcessor的作用

首先贴下BeanFactoryPostProcessor的源码吧

/**
 * Allows for custom modification of an application context's bean definitions,
 * adapting the bean property values of the context's underlying bean factory.
 *
 * <p>Application contexts can auto-detect BeanFactoryPostProcessor beans in
 * their bean definitions and apply them before any other beans get created.
 *
 * <p>Useful for custom config files targeted at system administrators that
 * override bean properties configured in the application context.
 *
 * <p>See PropertyResourceConfigurer and its concrete implementations
 * for out-of-the-box solutions that address such configuration needs.
 *
 * <p>A BeanFactoryPostProcessor may interact with and modify bean
 * definitions, but never bean instances. Doing so may cause premature bean
 * instantiation, violating the container and causing unintended side-effects.
 * If bean instance interaction is required, consider implementing
 * {@link BeanPostProcessor} instead.
 *
 * @author Juergen Hoeller
 * @since 06.07.2003
 * @see BeanPostProcessor
 * @see PropertyResourceConfigurer
 */
public interface BeanFactoryPostProcessor {

    /**
     * Modify the application context's internal bean factory after its standard
     * initialization. All bean definitions will have been loaded, but no beans
     * will have been instantiated yet. This allows for overriding or adding
     * properties even to eager-initializing beans.
     * @param beanFactory the bean factory used by the application context
     * @throws org.springframework.beans.BeansException in case of errors
     */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

首先请一字一句通读一下doc,其中两句话非常重要:

  • BeanFactoryPostProcessor允许使用者修改容器中的bean definitions
  • BeanFactoryPostProcessor可以与bean definitions打交道,但是千万不要进行bean实例化(感觉这里应该说的是不要在BeanFactoryPostProcessor进行可能触发bean实例化的操作)。这么做可能会导致bean被提前实例化,会破坏容器造成预估不到的副作用。如果你需要hack到bean实例化过程,请考虑使用BeanPostProcessor。

从doc中可以读到,BeanFactoryPostProcessor的主要作用是让你能接触到bean definitions,对bean definitions进行一定hack,但是也仅此而已了。绝对不允许在BeanFactoryPostProcessor中触发到bean的实例化!!! 为啥呢,doc说得很清楚but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. 下面就列举错误使用造成的两种典型“副作用”。

副作用1——使用注解进行依赖注入失败

贴一下示例代码片段吧。

@Component
public class PrematureBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<String, BBean> map = beanFactory.getBeansOfType(BBean.class);
        for (BBean bBean : map.values()) {
            assert bBean.getABean() == null;
        }
    }
}

@Component("bBean")
public class BBean {

    @Autowired
    private ABean aBean;

    public ABean getABean() {
        return aBean;
    }

}

@Component
public class ABean {

    private String name = "a";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

如上demo所示,在运行后,BBean中被期待注入的ABean最终为null。这是为啥呢?

贴一段ApplicationContext的启动代码吧

        public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

可以看到的是postProcessBeanFactory(beanFactory); 首先invoke了容器中的BeanFactoryPostProcessor实现类,其中当然就包括PrematureBeanFactoryPostProcessor,此时通过beanFactory.getBeansOfType触发了bean提前实例化。按理说,bean提前实例化也应该没问题的,aBean也应该是能够被注入的呀!那为啥最终不是这个结果呢。让我们研究下@Resource @AutoWired这种注解是如何注入依赖的,如何起作用的就明白了。@AutoWired起作用依赖AutowiredAnnotationBeanPostProcessor,@Resource依赖CommonAnnotationBeanPostProcessor,这俩都是BeanPostProcessor的实现。那BeanPostProcessors在何处被spring invoke呢,参见registerBeanPostProcessors(beanFactory);postProcessBeanFactory(beanFactory); 后面被调用,也就是说BBean被触发提前初始化的时候,AutowiredAnnotationBeanPostProcessor还没有被注册自然也不会被执行到,自然ABean=null。

PS:如果ABean和BBean都是通过xml的方式配置的则不会有上述问题(因为会执行setter setProperty),但强烈不建议这么做!

副作用2——可能会将ApplicationContext容器启动过程暴露在多线程之下

“将ApplicationContext容器启动过程暴露在多线程之下”倒不完全是因为错误使用BeanFactoryPostProcessor,而是错上加错。假设我们发现在副作用1的场景下aBean无法注入,而将BBean通过如下方式修改

@Component("bBean")
public class BBean implements ApplicationContextAware {

    private ApplicationContext context;

    @Autowired
    private ABean aBean;

    public ABean getABean() {
        return (ABean)context.getBean("aBean");
    }

    @Override
    public String toString() {
        return "BBean{" +
            "aBean=" + aBean +
            '}';
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}

这样问题就更大了,虽然在大多数的场景下不会有问题。但是如果BBean是类似于像DTS processer bean或者像是MetaQ的 listener bean情况就非常糟糕了。这种bean一旦被实例化+初始化之后就可以接受请求,如果在请求处理代码中触发了Spring bean的实例化过程,则此时的容器会被破坏,结果是无法预知的。而且问题和可能不太容易复现,因为只有在spring启动过程中请求进来触发ABean首次实例化才有可能会发生错误。强烈要求MetaQ listener DTSprocesser等bean依赖容器自身的依赖注入,不要认为干扰;如果实在要用listener or processor 中使用context.getBean("aBean")的方式,那也请做好listener or processor 的生命周期管理,保证在容器彻底起来之后才开始处理请求

总结

本文列举了BeanFactoryPostProcessor错误使用可能造成的问题。认真读doc,共勉!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Kubernetes

Kubernetes 1.8抢占式调

Author: xidianwangtao@gmail.com 阅读本博文前,建议先阅读解析Kubernetes 1.8中的基于Pod优先级的抢占式调度...

577140
来自专栏技术小黑屋

Android代码规范利器: Checkstyle

程序代码向来都不仅仅是用来运行的,写的一手好代码,易读,可维护应该是每个程序员所追求的。

15510
来自专栏osc同步分享-java技术分享站

springboot Actuator

springboot Actuator只需要加入依赖即可使用: <dependency> <groupId>org.springframework.bo...

30560
来自专栏菩提树下的杨过

ExtJs学习笔记(20)-利用ExtJs的Ajax与服务端WCF交互

ExtJs是一套非常不错的javascript UI库(第一次接触ExtJs的,可到官方网站http://www.extjs.com/deploy/dev/ex...

23170
来自专栏Android群英传

基于XDanmuku的Android性能优化实战

11320
来自专栏GIS讲堂

web中的树形结构【小结】

最近在做一个项目,是一个b/s架构的,在项目中,用到了树形结构,即如图1所示的结构。

34720
来自专栏架构师之旅

《Spring敲门砖之基础教程第一季》 第二章(1) Spring框架之IOC首例-HelloWorld

回顾 上一章我们主要学习了Spring的一些理论知识,对Spring框架有了一个总体的概括,大家应该在头脑里形成一个初步的印象,接下来我们就会针对Spring框...

210100
来自专栏Coding01

深入浅出 Laravel Echo (3)

看完 public channel 的流程,我们该来说说怎么跑通 private channel 了。

32330
来自专栏马洪彪

Java生成条码二维码

一、概述 可用barcode4j或zxing等第三方库,推荐zxing。 barcode4j资料链接:http://barcode4j.sourceforge....

52480
来自专栏积累沉淀

windows下hadoop-eclipse的编程所引起的一系列错误

解决Exception: org.apache.hadoop.io.nativeio.NativeIO$Windows.access0(Ljava/lang/S...

22850

扫码关注云+社区

领取腾讯云代金券