Spring事件处理——onApplicationEvent执行两次.md

我们知道Spring有两大类事件,一类是Application事件,超类是SpringApplicationEvent,这类事件是在Spring程序启动时,过程中分为几个阶段,每进行一个阶段,发出一个事件,依次对应ApplicationStartingEvent到ApplicationReadyEvent。标志着Application从启动开始到启动完成,各个阶段的分割点。参考Spring启动过程中Application事件的监听与处理。
另一类是ApplicationContext事件,超类是ApplicationContextEvent,表示ApplicationContext生命周期内的各个阶段。参考Spring的容器事件——ApplicationContextEvent的监听与处理
我们在监听上述两大类事件时,可能会遇到同一个事件之间两次的情况,为什么会出现这样的情况呢,下面具体来看看

1.现象复现

我们接着上篇文章Spring启动过程中Application事件的监听与处理,对于ApplicationEvent的监听:

@Component
public class ApplicationEventListener implements ApplicationListener{
    private Logger log = Logger.getLogger(this.getClass());
    @Override
    public void onApplicationEvent(SpringApplicationEvent event) {
     
        if(event instanceof ApplicationStartingEvent) {//启动之前
            log.info("处理ApplicationStartingEvent");
        }else if(event instanceof ApplicationReadyEvent ){//启动成功之后
        }
    }

运行之后,输出了两次"处理ApplicationStartingEvent"。说明来了两个ApplicationStartingEvent。
开启Debug模式,执行onApplicationEvent方法时,观察当前的ApplicationEventListener 对象,事件到来的顺序,对应的ApplicationEventListener地址:
1.ApplicationStartingEvent com.proudsmart.iot.listener.ApplicationEventListener@60dcc9fe
2.ApplicationStartingEvent com.proudsmart.iot.listener.ApplicationEventListener@443118b0
3.ApplicationEnvironmentPreparedEvent com.proudsmart.iot.listener.ApplicationEventListener@443118b0
4.ApplicationPreparedEvent com.proudsmart.iot.listener.ApplicationEventListener@443118b0
5.ApplicationStartedEvent com.proudsmart.iot.listener.ApplicationEventListener@443118b0
6.ApplicationReadyEvent com.proudsmart.iot.listener.ApplicationEventListener@443118b0
7.ApplicationEnvironmentPreparedEvent com.proudsmart.iot.listener.ApplicationEventListener@60dcc9fe

ApplicationReadyEvent 60dcc9fe com.proudsmart.iot.listener.ApplicationEventListener@60dcc9fe

观察下,ApplicationEventListener运行过程中生成了两个对象,一个是ApplicationEventListener@60dcc9fe,另一个是ApplicationEventListener@443118b0。
其中ApplicationEventListener@60dcc9fe先执行了ApplicationStartingEvent 事件,然后ApplicationEventListener@443118b0执行了Application整个生命周期的5个事件,接着ApplicationEventListener@60dcc9fe执行了剩下的4个事件。请记住这个执行顺序,第2节会分析。

一次是有启动参数的
第二次是没有启动参数的

2. 执行两次的原因

在没有答案之前,我从ApplicationEvent.getArgs()获得参数(启动时,我配置了–spring.profiles.active=discovery参数)。发现ApplicationEventListener@60dcc9fe也就是先创建的ApplicationEventListener对象,执行时能够获取到这个参数。后一个ApplicationEventListener获取参数为空。
基于参数和事件的两次执行顺序,我大胆猜想了下,第一个ApplicationEventListener是对应的root容器,第二个对应的可能是子容器。
然后上网查询了下,得到答案:
在web项目中如果同时集成了spring和springMVC的话,上下文中会存在两个容器,即spring的applicationContext.xml的父容器和springMVC的applicationContext-mvc.xml的子容器。这两个容器有相同的生命周期,所以同一个事件,在不同容器启动过程中都会发送一次。
如此,明白了多出的一次事件是引入了SpringMVC造成的。基于第一节中的时间执行顺序,我们可以得出这样的结论:

root容器启动开始–>创建子容器mvc并启动–>子容器mvc启动完成–>root容器继续启动–>root容器启动完成

3.解决方法

解决这个问题主要逻辑是,我们只关注root application context 的事件,处理它,而忽略mvc application context 的事件。那么问题变成了,如何区分是root Application context。
事件分为两类,每一类有不同的处理方法。

3.1 ApplicationEvent处理

ApplicationEvent提供的方法并不多,可能用到是getSpringApplication()
Spring事件处理——onApplicationEvent执行两次.md_第1张图片
我们看一下SpringApplication的方法,测试了几次发现并没有能够区分是否为root的标识。。。如果有哪位同学知道请告诉我一声,感激不尽!!
Spring事件处理——onApplicationEvent执行两次.md_第2张图片
所以我们只能走“旁门左道”,启动时加上启动参数,执行onApplicationEvent方法时,如果获取到的参数有值,则说明是root:


public void onApplicationEvent(SpringApplicationEvent event) {
     
        String[] args = event.getArgs();
        if(args == null || args.length ==0 ) {//MVC容器发出的事件不关注
            return ;
        }
        if(event instanceof ApplicationStartingEvent) {//启动之前
            log.info("处理ApplicationStartingEvent");
        }else if(event instanceof ApplicationReadyEvent ){//启动成功之后
        }
    }

3.1 ApplicationContextEvent处理

ApplicationContextEvent 有getApplicationContext()可以获取到上下文(就是容器),如果容器的父容器为空,那么他就是Root容器。


@Override  
public void onApplicationEvent(ApplicationContextEvent event) {  
    if(event.getApplicationContext().getParent() == null){ //root application context 
         //TODO
    }  
}  

你可能感兴趣的:(Java,SpringBoot)