Spring ApplicationListener ContextRefreshedEvent 多次执行问题及源码分析
2016年11月24日


我们可能会使用到如下方法,来在Spring初始化完成后,执行某些程序:

@Component
public class SpringContextRefreshedListener implements 
                ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedListener execute......");
        System.out.println(event.toString());
        System.out.println(event.getTimestamp());
        ApplicationContext application = event.getApplicationContext();
        ..... do something 
    }


但是,这个方法可能会执行多次!!


具体来说, 每加载完一次context,就会执行一次ContextRefreshedEvent

 * 而且每次执行,都会再执行一次parent的ContextRefreshedEvent

 * 也就是说,如果web.xml里面配置了两个Context,

 * 且两个Context都配置了ContextRefreshedEvent Listener,那么ContextRefreshedEvent会执行3次!

 * 而且更郁闷的是,后一个Context执行的两次ContextRefreshedEvent是一样的。


 具体源码分析:AbstractApplicationContext.publishEvent

if (this.parent != null) {
    if (this.parent instanceof AbstractApplicationContext) {
        ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
    }
    else {
        this.parent.publishEvent(event);
    }
}

parent的publishEvent,跟儿子的是一样的,所以两次事件效果一模一样。


网上很多人都遇到这个问题,但是解决方法都是 if (this.parent == null) {},治标不治本。


为了避免这种情况,根本的解决方法,就是  ApplicationListener<ContextRefreshedEvent> 应该和 ApplicationContext 一对一。如果有两个 ApplicationContext ,且两个ApplicationContext 都配置了ApplicationListener<ContextRefreshedEvent>,那么就应该执行两次。


如果想 ApplicationListener<ContextRefreshedEvent> 只执行一次,那就只应该把它配置在 其中一个ApplicationContext 中,另外一个ApplicationContext 不配置也就是说,使ApplicationListener<ContextRefreshedEvent>的实现类,只被其中一个ApplicationContext 加载到。

假如说是只给第二个ApplicationContext 配置了ApplicationListener<ContextRefreshedEvent>,名字叫MyApplicationListener,那么它会执行自己的ContextRefreshedEvent,此时MyApplicationListener被调用,也会调用parent,但是由于parent没有配置ApplicationListener<ContextRefreshedEvent>,所以不会有效果。那么最终就只有MyApplicationListener被调用了一次。这才是正确的解决方案。


参见另一篇文章:

Spring配置加载ContextLoaderListener和DispatcherServlet的区别和关系