前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用BeanFactoryPostProcessor——这种姿势不要用

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

作者头像
LNAmp
发布2018-09-05 15:19:53
2K0
发布2018-09-05 15:19:53
举报

使用BeanFactoryPostProcessor这种姿势不要用

前言

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

约定

实例化:instantiation 初始化:initialization

BeanFactoryPostProcessor的作用

首先贴下BeanFactoryPostProcessor的源码吧

代码语言:javascript
复制
/**
 * 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——使用注解进行依赖注入失败

贴一下示例代码片段吧。

代码语言:javascript
复制
@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的启动代码吧

代码语言:javascript
复制
        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通过如下方式修改

代码语言:javascript
复制
@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,共勉!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.03.21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 约定
  • BeanFactoryPostProcessor的作用
  • 副作用1——使用注解进行依赖注入失败
  • 副作用2——可能会将ApplicationContext容器启动过程暴露在多线程之下
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档