Spring Boot 源碼學習系列
ApplicationListener 詳解
- 引言
- 往期內容
- 主要內容
- 1. 初識 ApplicationListener
- 2. 加載 ApplicationListener
- 3. 響應應用程序事件
- 總結
引言
書接前文《初識 SpringApplication》,我們從 Spring Boot 的啟動類 SpringApplication
上入手,了解了 SpringApplication
實例化過程。其中,《BootstrapRegistryInitializer 詳解》 和 《ApplicationContextInitializer 詳解》博文中,Huazie 已經帶大家詳細分析了 BootstrapRegistryInitializer
和 ApplicationContextInitializer
的加載和初始化過程,如下還有 2.5 還未詳細分析:
那本篇博文就主要圍繞 2.5 的內容展開,詳細分析一下 ApplicationListener
的加載和處理應用程序事件的邏輯。
往期內容
在開始本篇的內容介紹之前,我們先來看看往期的系列文章【有需要的朋友,歡迎關注系列專欄】:
Spring Boot 源碼學習 |
Spring Boot 項目介紹 |
Spring Boot 核心運行原理介紹 |
【Spring Boot 源碼學習】@EnableAutoConfiguration 注解 |
【Spring Boot 源碼學習】@SpringBootApplication 注解 |
【Spring Boot 源碼學習】走近 AutoConfigurationImportSelector |
【Spring Boot 源碼學習】自動裝配流程源碼解析(上) |
【Spring Boot 源碼學習】自動裝配流程源碼解析(下) |
【Spring Boot 源碼學習】深入 FilteringSpringBootCondition |
【Spring Boot 源碼學習】OnClassCondition 詳解 |
【Spring Boot 源碼學習】OnBeanCondition 詳解 |
【Spring Boot 源碼學習】OnWebApplicationCondition 詳解 |
【Spring Boot 源碼學習】@Conditional 條件注解 |
【Spring Boot 源碼學習】HttpEncodingAutoConfiguration 詳解 |
【Spring Boot 源碼學習】RedisAutoConfiguration 詳解 |
【Spring Boot 源碼學習】JedisConnectionConfiguration 詳解 |
【Spring Boot 源碼學習】初識 SpringApplication |
【Spring Boot 源碼學習】Banner 信息打印流程 |
【Spring Boot 源碼學習】自定義 Banner 信息打印 |
【Spring Boot 源碼學習】BootstrapRegistryInitializer 詳解 |
【Spring Boot 源碼學習】ApplicationContextInitializer 詳解 |
主要內容
注意: 以下涉及 Spring Boot 源碼 均來自版本 2.7.9,其他版本有所出入,可自行查看源碼。
1. 初識 ApplicationListener
我們先來看看 ApplicationListener
接口的源碼【spring-context-5.3.25.jar】:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {return event -> consumer.accept(event.getPayload());}
}
從上述代碼,我們可以看到 ApplicationListener
接口被 @FunctionalInterface
注解修飾。
知識點:
@FunctionalInterface
是 Java 8 中引入的一個注解,用于標識一個函數式接口。函數式接口是只有一個抽象方法的接口,常用于實現 Lambda 表達式和方法引用。
使用@FunctionalInterface
注解可以向編譯器指示該接口是一個函數式接口,從而在編譯時進行類型檢查,確保該接口 只包含一個抽象方法。此外,該注解還可以為函數式接口生成特殊的方法,如默認方法(default method)和 靜態方法(static method),這些方法可以在接口中提供更多的功能,這里就不贅述了,感興趣的朋友可以自行查閱相關函數式接口的資料。
ApplicationListener
是 Spring 中應用程序事件監聽器實現的接口。它基于觀察者設計模式的java.util.EventListener
接口的標準。在注冊到 Spring ApplicationContext 時,事件將進行相應的過濾,只有匹配的事件對象才會使該監聽器被調用。
在 ApplicationListener
接口中,我們可以看到它定義了一個 onApplicationEvent(E event)
方法,當監聽事件被觸發時,onApplicationEvent
方法就會被調用執行。onApplicationEvent
方法一般用于處理應用程序事件,參數 event
為 ApplicationEvent
的子類,也就是具體要響應處理的各種類型的應用程序事件。例如,當某個特定事件發生時,你可能想要記錄日志、更新數據庫、發送電子郵件等等。
另外,ApplicationListener
接口還提供了一個靜態方法 forPayload(Consumer<T> consumer)
,用于創建一個新的 ApplicationListener
實例。這個方法接受一個 Consumer<T>
類型的參數,這個參數是一個函數接口,它接受一個泛型參數 T
,并對其執行一些操作。通過這個方法,你可以將一個 Consumer
函數作為參數,然后返回一個對應的事件監聽器。這個監聽器會在事件發生時,調用 Consumer
函數處理事件的有效載荷【即事件中包含的有效信息或數據】。
2. 加載 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
上述代碼是 SpringApplication
的核心構造方法中的邏輯,它用于加載實現了 ApplicationListener
接口的監聽器實例集合,并將該監聽器實例集合設置到 SpringApplication
的 listeners
變量中。
private List<ApplicationContextInitializer<?>> initializers;
我們進入 getSpringFactoriesInstances
方法,查看如下:
我們看到了如下的代碼 :
SpringFactoriesLoader.loadFactoryNames(type, classLoader);
這里是通過 SpringFactoriesLoader
類的 loadFactoryNames
方法來獲取 META-INF/spring.factories
中配置 key 為 org.springframework.context.ApplicationListener
的數據;
我們以 spring-boot-autoconfigure-2.7.9.jar 為例:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
3. 響應應用程序事件
這里我們需要查看 SpringApplication
的 run(String... args)
方法,如下所示:
我們看上面的 SpringApplicationRunListeners
,其內的 listeners
變量是 SpringApplicationRunListener
接口的集合,如下所示:
而 SpringApplicationRunListener
接口的一個實現就是 EventPublishingRunListener
類,該類的作用就是根據 Spring Boot 程序啟動過程的 不同階段 發布對應的事件,然后由不同的實現 ApplicationListener
接口的應用程序監聽器,來處理對應的事件【有關 SpringApplicationRunListener
監聽器的內容,我們后續博文中會詳細介紹,這里不展開了】。
如下圖是 SpringApplicationRunListeners
類中的方法,它們分別對應了 Spring Boot 程序啟動過程中要發布的不同階段的事件的邏輯。
starting
:當run
方法第一次被執行時,該方法會立即被調用,可用于非常早期的初始化工作environmentPrepared
:當environment
準備完成,在ApplicationContext
創建之前,該方法被調用contextPrepared
:當ApplicationContext
構建完成,資源還未被加載時,該方法被調用contextLoaded
:當ApplicationContext
加載完成,未被刷新之前,該方法被調用started
:當ApplicationContext
刷新并啟動之后,CommandLineRunner
和ApplicationRunner
未被調用之前,該方法被調用ready
:當所有準備工作就緒,run
方法執行完成之前,該方法被調用failed
:當應用程序出現錯誤時,該方法被調用
我們以 starting
方法的邏輯為例,看一下 ApplicationStartingEvent
事件發布并被處理的過程。
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),(step) -> {if (mainApplicationClass != null) {step.tag("mainApplicationClass", mainApplicationClass.getName());}});
}
我們繼續看 doWithListeners
方法:
結合上面的截圖,我們重點看下這行:
(listener) -> listener.starting(bootstrapContext)
這里時調用了 SpringApplicationRunListener
接口的 starting
方法:
這里的 multicastEvent
方法就是用來發布一個指定的應用程序事件,比如這里發布的就是 ApplicationStartingEvent
事件。
總結
本篇 Huazie 帶大家詳細分析了 ApplicationListener
的加載和處理應用程序事件,這對于后續的 SpringApplication
運行流程的理解至關重要。