我们可能会使用到如下方法,来在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的区别和关系》