1. 程式人生 > >Spring事件處理——onApplicationEvent執行兩次.md

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 [email protected]
2.ApplicationStartingEvent [email protected]
3.ApplicationEnvironmentPreparedEvent

[email protected]
4.ApplicationPreparedEvent [email protected]
5.ApplicationStartedEvent [email protected]
6.ApplicationReadyEvent [email protected]
7.ApplicationEnvironmentPreparedEvent [email protected]

ApplicationReadyEvent 60dcc9fe [email protected]

觀察下,ApplicationEventListener執行過程中生成了兩個物件,一個是[email protected],另一個是[email protected]
其中[email protected]先執行了ApplicationStartingEvent 事件,然後[email protected]執行了Application整個生命週期的5個事件,接著[email protected]執行了剩下的4個事件。請記住這個執行順序,第2節會分析。

一次是有啟動引數的
第二次是沒有啟動引數的

2. 執行兩次的原因

在沒有答案之前,我從ApplicationEvent.getArgs()獲得引數(啟動時,我配置了–spring.profiles.active=discovery引數)。發現[email protected]也就是先建立的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
    }  
}