SpringBoot-SpringBoot中的事件機制

glmapper發表於2019-01-01

微信公眾號:glmapper工作室
掘金專欄:glmapper
微 博:瘋狂的石頭_henu
歡迎關注,一起學習分享技術

在這篇文章中聊一聊 Spring 中的擴充套件機制(一)中對Spring中的事件機制進行了分析。那麼對於 SpringBoot 來說,它在 Spring 的基礎上又做了哪些擴充呢?本篇將來聊一聊 SpringBoot 中的事件。

在 SpringBoot 的啟動過程中,會通過 SPI 機制去載入 spring.factories 下面的一些類,這裡面就包括了事件相關的類。

  • SpringApplicationRunListener
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
複製程式碼
  • ApplicationListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
複製程式碼

SpringApplicationRunListener 類是 SpringBoot 中新增的類。SpringApplication 類 中使用它們來間接呼叫 ApplicationListener。另外還有一個新增的類是SpringApplicationRunListenersSpringApplicationRunListeners 中包含了多個 SpringApplicationRunListener

SpringApplicationRunListener

SpringApplicationRunListener 介面規定了 SpringBoot 的生命週期,在各個生命週期廣播相應的事件,呼叫實際的 ApplicationListener 類。通過對 SpringApplicationRunListener 的分析,也可以對 SpringBoot 的整個啟動過程的理解會有很大幫助。

先來看下SpringApplicationRunListener 介面的程式碼:

public interface SpringApplicationRunListener {
	//當run方法首次啟動時立即呼叫。可用於非常早期的初始化。
	void starting();
	//在準備好環境後,但在建立ApplicationContext之前呼叫。
	void environmentPrepared(ConfigurableEnvironment environment);
	//在建立和準備好ApplicationContext之後,但在載入源之前呼叫。
	void contextPrepared(ConfigurableApplicationContext context);
	//在載入應用程式上下文後但重新整理之前呼叫
	void contextLoaded(ConfigurableApplicationContext context);
	//上下文已重新整理,應用程式已啟動,但尚未呼叫commandlinerunner和applicationrunner。
	void started(ConfigurableApplicationContext context);
	//在執行方法完成之前立即呼叫,此時應用程式上下文已重新整理,
	//並且所有commandlinerunner和applicationrunner都已呼叫。
	//2.0 才有
	void running(ConfigurableApplicationContext context);
	//在執行應用程式時發生故障時呼叫。2.0 才有
	void failed(ConfigurableApplicationContext context, Throwable exception);
}
複製程式碼

SpringApplicationRunListeners

上面提到,SpringApplicationRunListenersSpringApplicationRunListener的集合,裡面包括了很多SpringApplicationRunListener例項;SpringApplication 類實際上使用的是 SpringApplicationRunListeners 類,與 SpringApplicationRunListener 生命週期相同,呼叫各個週期的 SpringApplicationRunListener 。然後廣播相應的事件到 ApplicationListener

程式碼詳見:SpringApplicationRunListeners.

EventPublishingRunListener

EventPublishingRunListener 類是 SpringApplicationRunListener介面的實現類 ,它具有廣播事件的功能。其內部使用 ApplicationEventMulticaster在實際重新整理上下文之前釋出事件。下面來看下 EventPublishingRunListener 類生命週期對應的事件。

ApplicationStartingEvent

ApplicationStartingEventSpringBoot 啟動開始的時候執行的事件,在該事件中可以獲取到 SpringApplication 物件,可做一些執行前的設定,對應的呼叫方法是 starting()

ApplicationEnvironmentPreparedEvent

ApplicationEnvironmentPreparedEventSpringBoot 對應 Enviroment 已經準備完畢時執行的事件,此時上下文 context 還沒有建立。在該監聽中獲取到 ConfigurableEnvironment 後可以對配置資訊做操作,例如:修改預設的配置資訊,增加額外的配置資訊等。對應的生命週期方法是 environmentPrepared(environment)SpringCloud 中,引導上下文就是在這時初始化的。

ApplicationContextInitializedEvent

SpringApplication 啟動並且準備好 ApplicationContext,並且在載入任何 bean 定義之前呼叫了 ApplicationContextInitializers 時釋出的事件。對應的生命週期方法是contextPrepared()

ApplicationPreparedEvent

ApplicationPreparedEventSpringBoot上下文 context 建立完成是釋出的事件;但此時 spring 中的 bean 還沒有完全載入完成。這裡可以將上下文傳遞出去做一些額外的操作。但是在該監聽器中是無法獲取自定義 bean 並進行操作的。對應的生命週期方法是 contextLoaded()

ApplicationStartedEvent

這個事件是在 2.0 版本才引入的;具體釋出是在應用程式上下文重新整理之後,呼叫任何 ApplicationRunnerCommandLineRunner 執行程式之前。

ApplicationReadyEvent

這個和 ApplicationStartedEvent 很類似,也是在應用程式上下文重新整理之後之後呼叫,區別在於此時ApplicationRunnerCommandLineRunner已經完成呼叫了,也意味著 SpringBoot 載入已經完成。

ApplicationFailedEvent

SpringBoot 啟動異常時執行的事件,在異常發生時,最好是新增虛擬機器對應的鉤子進行資源的回收與釋放,能友善的處理異常資訊。

demo 及各個事件的執行順序

下面的各個事件對應的demo及列印出來的執行順序。

  • GlmapperApplicationStartingEventListener
public class GlmapperApplicationStartingEventListener implements ApplicationListener<ApplicationStartingEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartingEvent applicationStartingEvent) {
        System.out.println("execute ApplicationStartingEvent ...");
    }
}
複製程式碼
  • GlmapperApplicationEnvironmentPreparedEvent
public class GlmapperApplicationEnvironmentPreparedEvent implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
        System.out.println("execute ApplicationEnvironmentPreparedEvent ...");
    }
}
複製程式碼
  • GlmapperApplicationContextInitializedEvent
public class GlmapperApplicationContextInitializedEvent implements ApplicationListener<ApplicationContextInitializedEvent> {
    @Override
    public void onApplicationEvent(ApplicationContextInitializedEvent applicationContextInitializedEvent) {
        System.out.println("execute applicationContextInitializedEvent ...");
    }
}
複製程式碼
  • GlmapperApplicationPreparedEvent
public class GlmapperApplicationPreparedEvent implements ApplicationListener<ApplicationPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) {
        System.out.println("execute ApplicationPreparedEvent ...");
    }
}
複製程式碼
  • GlmapperApplicationStartedEvent
public class GlmapperApplicationStartedEvent implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
        System.out.println("execute ApplicationStartedEvent ...");
    }
}
複製程式碼
  • GlmapperApplicationReadyEvent
public class GlmapperApplicationReadyEvent implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        System.out.println("execute ApplicationReadyEvent ...");
    }
}
複製程式碼
  • 執行結果

SpringBoot-SpringBoot中的事件機制

SpringBoot 中的事件體系

這裡圍繞 SpringApplicationRunListener 這個類來說。在實現類 EventPublishingRunListener 中,事件釋出有兩種模式:

  • 通過 SimpleApplicationEventMulticaster 進行事件廣播
  • 所有監聽器交給相應的 Context

所以EventPublishingRunListener 不僅負責釋出事件,而且在合適的時機將 SpringApplication 所獲取的監聽器和應用上下文作關聯。

SimpleApplicationEventMulticaster

SimpleApplicationEventMulticasterSpring 預設的事件廣播器。來看下它是怎麼工作的:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        Executor executor = getTaskExecutor();
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}
複製程式碼

從上面的程式碼段可以看出,它是通過遍歷註冊的每個監聽器,並啟動來呼叫每個監聽器的 onApplicationEvent 方法。

下面再來看下 SimpleApplicationEventMulticaster 的類整合結構:

SpringBoot-SpringBoot中的事件機制
這裡的 AbstractApplicationContext 下面來聊,這個類實際上就負責了事件體系的初始化工作。

事件體系的初始化

事件體系的初始化對應在 SpringBoot啟動過程的 refreshContext這個方法;refreshContext具體呼叫 AbstractApplicationContext.refresh()方法,最後呼叫 initApplicationEventMulticaster() 來完成事件體系的初始化,程式碼如下:

SpringBoot-SpringBoot中的事件機制

使用者可以為容器定義一個自定義的事件廣播器,只要實現 ApplicationEventMulticaster 就可以了,Spring 會通過 反射的機制將其註冊成容器的事件廣播器,如果沒有找到配置的外部事件廣播器,Spring 就是預設使用 SimpleApplicationEventMulticaster 作為事件廣播器。

事件註冊

事件註冊是在事件體系初始化完成之後做的事情,也是在 AbstractApplicationContext.refresh() 方法中進行呼叫的。

SpringBoot-SpringBoot中的事件機制
這裡幹了三件事:

  • 首先註冊靜態指定的 listeners;這裡包括我們自定義的那些監聽器。
  • 呼叫 DefaultListableBeanFactorygetBeanNamesForType 得到自定義的 ApplicationListener bean 進行事件註冊。
  • 廣播早期的事件。

事件廣播

事件釋出伴隨著 SpringBoot 啟動的整個生命週期。不同階段對應釋出不同的事件,上面我們已經對各個事件進行了分析,下面就具體看下發布事件的實現:

org.springframework.context.support.AbstractApplicationContext#publishEvent

SpringBoot-SpringBoot中的事件機制

earlyApplicationEvents 中的事件是廣播器未建立的時候儲存通知資訊,一旦容器建立完成,以後都是直接通知。

廣播事件最終還是通過呼叫 ApplicationEventMulticastermulticastEvent 來實現。而 multicastEvent 也就就是事件執行的方法。

事件執行

上面 SimpleApplicationEventMulticaster 小節已經初步介紹了 multicastEvent 這個方法。補充一點, 如果有可用的 taskExecutor 會使用併發的模式執行事件,但是實際上 SimpleApplicationEventMulticaster 並沒有提供執行緒池實現,預設請況下是使用同步的方式執行事件(org.springframework.core.task.SyncTaskExecutor),所以如果需要非同步配置的話,需要自己去實現執行緒池。

SpringBoot 啟動過程中的事件階段

這裡回到 SpringApplicationrun方法,看下 SpringBoot 在啟動過程中,各個事件階段做了哪些事情。

starting -> ApplicationStartingEvent

這裡 debugstarting 方法,追蹤到 multicastEvent,這裡 typeApplicationStartingEvent;對應的事件如下:

SpringBoot-SpringBoot中的事件機制

  • LoggerApplicationListener:配置日誌系統。使用logging.config環境變數指定的配置或者預設配置
  • BackgroundPreinitializer:儘早觸發一些耗時的初始化任務,使用一個後臺執行緒
  • DelegatingApplicationListener:監聽到事件後轉發給環境變數context.listener.classes指定的那些事件監聽器
  • LiquibaseServiceLocatorApplicationListener:使用一個可以和 SpringBoot 可執行jar包配合工作的版本替換 liquibase ServiceLocator

listeners.environmentPrepared->ApplicationEnvironmentPreparedEvent

SpringBoot-SpringBoot中的事件機制

  • AnsiOutputApplicationListener:根據spring.output.ansi.enabled引數配置AnsiOutput

  • ConfigFileApplicationListener:EnvironmentPostProcessor,從常見的那些約定的位置讀取配置檔案,比如從以下目錄讀取application.properties,application.yml等配置檔案:

    • classpath:
    • file:.
    • classpath:config
    • file:./config/

    也可以配置成從其他指定的位置讀取配置檔案。

  • ClasspathLoggingApplicationListener:對環境就緒事件ApplicationEnvironmentPreparedEvent/應用失敗事件ApplicationFailedEvent做出響應,往日誌DEBUG級別輸出TCCL(thread context class loader)classpath

  • FileEncodingApplicationListener:如果系統檔案編碼和環境變數中指定的不同則終止應用啟動。具體的方法是比較系統屬性file.encoding和環境變數spring.mandatory-file-encoding是否相等(大小寫不敏感)。

listeners.contextPrepared->ApplicationContextInitializedEvent

SpringBoot-SpringBoot中的事件機制
相關監聽器參考上面的描述。

listeners.contextLoaded->ApplicationPreparedEvent

SpringBoot-SpringBoot中的事件機制
相關監聽器參考上面的描述。

refresh->ContextRefreshedEvent

SpringBoot-SpringBoot中的事件機制

  • ConditionEvaluationReportLoggingListener:實際上實現的是 ApplicationContextInitializer介面,其目的是將 ConditionEvaluationReport 寫入到日誌,使用DEBUG級別輸出。程式崩潰報告會觸發一個訊息輸出,建議使用者使用除錯模式顯示報告。它是在應用初始化時繫結一個ConditionEvaluationReportListener事件監聽器,然後相應的事件發生時輸出ConditionEvaluationReport報告。
  • ClearCachesApplicationListener:應用上下文載入完成後對快取做清除工作,響應事件ContextRefreshedEvent
  • SharedMetadataReaderFactoryContextInitializer: 向context註冊了一個BeanFactoryPostProcessorCachingMetadataReaderFactoryPostProcessor例項。
  • ResourceUrlProvider:handling mappings處理

started->ApplicationStartedEvent

SpringBoot-SpringBoot中的事件機制
相關監聽器參考上面的描述。

running->ApplicationReadyEvent

SpringBoot-SpringBoot中的事件機制
相關監聽器參考上面的描述。

BackgroundPreinitializer&DelegatingApplicationListener

這兩個貫穿了整個過程,這裡拎出來單獨解釋下:

  • BackgroundPreinitializer:對於一些耗時的任務使用一個後臺執行緒儘早觸發它們開始執行初始化,這是SpringBoot的預設行為。這些初始化動作也可以叫做預初始化。可以通過設定系統屬性spring.backgroundpreinitializer.ignoretrue可以禁用該機制。該機制被禁用時,相應的初始化任務會發生在前臺執行緒。
  • DelegatingApplicationListener:監聽應用事件,並將這些應用事件廣播給環境屬性context.listener.classes指定的那些監聽器。

小結

到此,SpringBoot 中的事件相關的東西就結束了。本文從SpringApplicationRunListener這個類說起,接著介紹 SpringBoot 啟動過程的事件以及事件的生命週期。最後介紹了 SpringBoot中的內建的這些 監聽器在啟動過程中對應的各個階段。

新年伊始,祝大家新年快樂!

參考

相關文章