美文网首页Springspingboot
使用BeanFactoryPostProcessor——这种姿势

使用BeanFactoryPostProcessor——这种姿势

作者: LNAmp | 来源:发表于2018-03-21 13:34 被阅读137次

    使用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,共勉!

    相关文章

      网友评论

      • a503ca8d550e:作者可以举一些实际工作中使用BeanPostProcessor的例子吗?谢谢~
      • 4b7ce6677af3:兄弟,应该写错了点东西吧,refresh方法中的postProcessBeanFactory不是干那个事情的啊,用你的一句话,请认真阅读这局代码上的DOC,按照你的意思,你想说的应该是他下面那个方法:invokeBeanFactoryPostProcessors(beanFactory);

      本文标题:使用BeanFactoryPostProcessor——这种姿势

      本文链接:https://www.haomeiwen.com/subject/lrcgqftx.html