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<SpringApplicationEvent >{
    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()
在这里插入图片描述
我们看一下SpringApplication的方法,测试了几次发现并没有能够区分是否为root的标识。。。如果有哪位同学知道请告诉我一声,感激不尽!!
在这里插入图片描述
所以我们只能走“旁门左道”,启动时加上启动参数,执行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
    }  
}  
onApplicationEvent是一个方法,用于处理Spring应用程序的事件。在这个方法中,我们可以根据不同的事件类型执行相应的逻辑。在引用\[1\]中的代码示例中,onApplicationEvent方法中的逻辑是判断事件类型,并根据不同的事件类型执行相应的处理。例如,如果事件是ApplicationStartingEvent,则会输出"处理ApplicationStartingEvent"。如果事件是ApplicationReadyEvent,则可以执行启动成功后的逻辑。\[1\] 在引用\[2\]中的代码示例中,我们可以看到onApplicationEvent方法被执行两次,分别对应两个不同的ApplicationEventListener对象。第一个对象先执行了ApplicationStartingEvent事件,然后第二个对象执行了Application整个生命周期的5个事件,最后第一个对象执行了剩下的4个事件。这个执行顺序是根据事件的触发顺序来确定的。\[2\] 在引用\[3\]中的代码示例中,我们可以看到一个实现了ApplicationListener接口的ApplicationEventListener类。在这个类中,通过重写onApplicationEvent方法来处理不同的事件。例如,如果事件是ApplicationStartingEvent,则会处理该事件。\[3\] 总结来说,onApplicationEvent方法是用来处理Spring应用程序的事件的,我们可以根据不同的事件类型执行相应的逻辑。在引用\[1\]中的代码示例中,onApplicationEvent方法根据事件类型输出不同的信息。在引用\[2\]中的代码示例中,我们可以看到onApplicationEvent方法被执行两次,分别对应两个不同的ApplicationEventListener对象。在引用\[3\]中的代码示例中,我们可以看到一个实现了ApplicationListener接口的ApplicationEventListener类,通过重写onApplicationEvent方法来处理不同的事件。 #### 引用[.reference_title] - *1* *2* *3* [Spring事件处理&mdash;&mdash;onApplicationEvent执行两次.md](https://blog.csdn.net/u014453515/article/details/85268526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值