1. 程式人生 > >SpringBoot內建生命週期事件詳解 SpringBoot原始碼(十)

SpringBoot內建生命週期事件詳解 SpringBoot原始碼(十)

**SpringBoot中文註釋專案Github地址:** https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 本篇接 [SpringBoot事件監聽機制原始碼分析(上) SpringBoot原始碼(九)](https://juejin.im/post/5e92cde8f265da47e22f1fdb) # 1 溫故而知新 溫故而知新,我們來簡單回顧一下上篇的內容,上一篇我們分析了**SpringBoot啟動時廣播生命週期事件的原理**,現將關鍵步驟再濃縮總結下: 1. 為廣播SpringBoot內建生命週期事件做前期準備:1)首先載入`ApplicationListener`監聽器實現類;2)其次載入SPI擴充套件類`EventPublishingRunListener`。 2. SpringBoot啟動時利用`EventPublishingRunListener`廣播生命週期事件,然後`ApplicationListener`監聽器實現類監聽相應的生命週期事件執行一些初始化邏輯的工作。 # 2 引言 上篇文章的側重點是分析了SpringBoot啟動時廣播生命週期事件的原理,此篇文章我們再來詳細分析SpringBoot內建的7種生命週期事件的原始碼。 # 3 SpringBoot生命週期事件原始碼分析 分析SpringBoot的生命週期事件,我們先來看一張類結構圖: ![](https://user-gold-cdn.xitu.io/2020/5/2/171d3520a8eec9ee?w=1172&h=626&f=png&s=56346) 由上圖可以看到事件類之間的關係: 1. 最頂級的父類是JDK的事件基類`EventObject`; 2. 然後Spring的事件基類`ApplicationEvent`繼承了JDK的事件基類`EventObject`; 3. 其次SpringBoot的生命週期事件基類`SpringApplicationEvent`繼承了Spring的事件基類`ApplicationEvent`; 4. 最後SpringBoot具體的7個生命週期事件類再繼承了SpringBoot的生命週期事件基類`SpringApplicationEvent`。 # 3.1 JDK的事件基類EventObject `EventObject`類是JDK的事件基類,可以說是所有Java事件類的基本,即所有的Java事件類都直接或間接繼承於該類,原始碼如下: ```java // EventObject.java public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; /** * The object on which the Event initially occurred. */ protected transient Object source; /** * Constructs a prototypical Event. * * @param source The object on which the Event initially occurred. * @exception IllegalArgumentException if source is null. */ public EventObject(Object source) { if (source == null) throw new IllegalArgumentException("null source"); this.source = source; } /** * The object on which the Event initially occurred. * * @return The object on which the Event initially occurred. */ public Object getSource() { return source; } /** * Returns a String representation of this EventObject. * * @return A a String representation of this EventObject. */ public String toString() { return getClass().getName() + "[source=" + source + "]"; } } ``` 可以看到`EventObject`類只有一個屬性`source`,這個屬性是用來記錄最初事件是發生在哪個類,舉個栗子,比如在SpringBoot啟動過程中會發射`ApplicationStartingEvent`事件,而這個事件最初是在`SpringApplication`類中發射的,因此`source`就是`SpringApplication`物件。 # 3.2 Spring的事件基類ApplicationEvent `ApplicationEvent`繼承了DK的事件基類`EventObject`類,是Spring的事件基類,被所有Spring的具體事件類繼承,原始碼如下: ```java // ApplicationEvent.java /** * Class to be extended by all application events. Abstract as it * doesn't make sense for generic events to be published directly. * * @author Rod Johnson * @author Juergen Hoeller */ public abstract class ApplicationEvent extends EventObject { /** use serialVersionUID from Spring 1.2 for interoperability. */ private static final long serialVersionUID = 7099057708183571937L; /** System time when the event happened. */ private final long timestamp; /** * Create a new ApplicationEvent. * @param source the object on which the event initially occurred (never {@code null}) */ public ApplicationEvent(Object source) { super(source); this.timestamp = System.currentTimeMillis(); } /** * Return the system time in milliseconds when the event happened. */ public final long getTimestamp() { return this.timestamp; } } ``` 可以看到`ApplicationEvent`有且僅有一個屬性`timestamp`,該屬性是用來記錄事件發生的時間。 # 3.3 SpringBoot的事件基類SpringApplicationEvent `SpringApplicationEvent`類繼承了Spring的事件基類`ApplicationEvent`,是所有SpringBoot內建生命週期事件的父類,原始碼如下: ```java /** * Base class for {@link ApplicationEvent} related to a {@link SpringApplication}. * * @author Phillip Webb */ @SuppressWarnings("serial") public abstract class SpringApplicationEvent extends ApplicationEvent { private final String[] args; public SpringApplicationEvent(SpringApplication application, String[] args) { super(application); this.args = args; } public SpringApplication getSpringApplication() { return (SpringApplication) getSource(); } public final String[] getArgs() { return this.args; } } ``` 可以看到`SpringApplicationEvent`有且僅有一個屬性`args`,該屬性就是SpringBoot啟動時的命令列引數即標註`@SpringBootApplication`啟動類中`main`函式的引數。 # 3.4 SpringBoot具體的生命週期事件類 接下來我們再來看一下`SpringBoot`內建生命週期事件即`SpringApplicationEvent`的具體子類們。 # 3.4.1 ApplicationStartingEvent ```java // ApplicationStartingEvent.java public class ApplicationStartingEvent extends SpringApplicationEvent { public ApplicationStartingEvent(SpringApplication application, String[] args) { super(application, args); } } ``` SpringBoot開始啟動時便會發布`ApplicationStartingEvent`事件,其釋出時機在環境變數Environment或容器ApplicationContext建立前但在註冊`ApplicationListener`具體監聽器之後,標誌標誌`SpringApplication`開始啟動。 # 3.4.2 ApplicationEnvironmentPreparedEvent ```java // ApplicationEnvironmentPreparedEvent.java public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent { private final ConfigurableEnvironment environment; /** * Create a new {@link ApplicationEnvironmentPreparedEvent} instance. * @param application the current application * @param args the arguments the application is running with * @param environment the environment that was just created */ public ApplicationEnvironmentPreparedEvent(SpringApplication application, String[] args, ConfigurableEnvironment environment) { super(application, args); this.environment = environment; } /** * Return the environment. * @return the environment */ public ConfigurableEnvironment getEnvironment() { return this.environment; } } ``` 可以看到`ApplicationEnvironmentPreparedEvent`事件多了一個`environment`屬性,我們不妨想一下,多了`environment`屬性的作用是啥? 答案就是`ApplicationEnvironmentPreparedEvent`事件的`environment`屬性作用是利用事件釋出訂閱機制,相應監聽器們可以從`ApplicationEnvironmentPreparedEvent`事件中取出`environment`變數,然後我們可以為`environment`屬性增加屬性值或讀出`environment`變數中的值。 > **舉個栗子:** `ConfigFileApplicationListener`監聽器就是監聽了`ApplicationEnvironmentPreparedEvent`事件,然後取出`ApplicationEnvironmentPreparedEvent`事件的`environment`屬性,然後再為`environment`屬性增加`application.properties`配置檔案中的環境變數值。 當SpringApplication已經開始啟動且環境變數`Environment`已經建立後,並且為環境變數`Environment`配置了命令列和`Servlet`等型別的環境變數後,此時會發布`ApplicationEnvironmentPreparedEvent`事件。 監聽`ApplicationEnvironmentPreparedEvent`事件的第一個監聽器是`ConfigFileApplicationListener`,因為是`ConfigFileApplicationListener`監聽器還要為環境變數`Environment`增加`application.properties`配置檔案中的環境變數;此後還有一些也是監聽`ApplicationEnvironmentPreparedEvent`事件的其他監聽器監聽到此事件時,此時可以說環境變數`Environment`幾乎已經完全準備好了。 > **思考:** 監聽同一事件的監聽器們執行監聽邏輯時是有順序的,我們可以想一下這個排序邏輯是什麼時候排序的?還有為什麼要這樣排序呢? # 3.4.3 ApplicationContextInitializedEvent ```java // ApplicationContextInitializedEvent.java public class ApplicationContextInitializedEvent extends SpringApplicationEvent { private final ConfigurableApplicationContext context; /** * Create a new {@link ApplicationContextInitializedEvent} instance. * @param application the current application * @param args the arguments the application is running with * @param context the context that has been initialized */ public ApplicationContextInitializedEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context) { super(application, args); this.context = context; } /** * Return the application context. * @return the context */ public ConfigurableApplicationContext getApplicationContext() { return this.context; } } ``` 可以看到`ApplicationContextInitializedEvent`事件多了個`ConfigurableApplicationContext`型別的`context`屬性,`context`屬性的作用同樣是為了相應監聽器可以拿到這個`context`屬性執行一些邏輯,具體作用將在`3.4.4`詳述。 `ApplicationContextInitializedEvent`事件在`ApplicationContext`容器建立後,且為`ApplicationContext`容器設定了`environment`變數和執行了`ApplicationContextInitializers`的初始化方法後但在bean定義載入前觸發,標誌ApplicationContext已經初始化完畢。 > **擴充套件:** 可以看到`ApplicationContextInitializedEvent`是在為`context`容器配置`environment`變數後觸發,此時`ApplicationContextInitializedEvent`等事件只要有`context`容器的話,那麼其他需要`environment`環境變數的監聽器只需要從`context`中取出`environment`變數即可,從而`ApplicationContextInitializedEvent`等事件沒必要再配置`environment`屬性。 # 3.4.4 ApplicationPreparedEvent ```java // ApplicationPreparedEvent.java public class ApplicationPreparedEvent extends SpringApplicationEvent { private final ConfigurableApplicationContext context; /** * Create a new {@link ApplicationPreparedEvent} instance. * @param application the current application * @param args the arguments the application is running with * @param context the ApplicationContext about to be refreshed */ public ApplicationPreparedEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context) { super(application, args); this.context = context; } /** * Return the application context. * @return the context */ public ConfigurableApplicationContext getApplicationContext() { return this.context; } } ``` 同樣可以看到`ApplicationPreparedEvent`事件多了個`ConfigurableApplicationContext`型別的`context`屬性,多了`context`屬性的作用是能讓監聽該事件的監聽器們能拿到`context`屬性,監聽器拿到`context`屬性一般有如下作用: 1. 從事件中取出`context`屬性,然後可以增加一些後置處理器,比如`ConfigFileApplicationListener`監聽器監聽到`ApplicationPreparedEvent`事件後,然後取出`context`變數,通過`context`變數增加了`PropertySourceOrderingPostProcessor`這個後置處理器; 2. 通過`context`屬性取出`beanFactory`容器,然後註冊一些`bean`,比如`LoggingApplicationListener`監聽器通過`ApplicationPreparedEvent`事件的`context`屬性取出`beanFactory`容器,然後註冊了`springBootLoggingSystem`這個單例`bean`; 3. 通過`context`屬性取出`Environment`環境變數,然後就可以操作環境變數,比如`PropertiesMigrationListener`。 `ApplicationPreparedEvent`事件在`ApplicationContext`容器已經完全準備好時但在容器重新整理前觸發,在這個階段`bean`定義已經載入完畢還有`environment`已經準備好可以用了。 # 3.4.5 ApplicationStartedEvent ```java // ApplicationStartedEvent.java public class ApplicationStartedEvent extends SpringApplicationEvent { private final ConfigurableApplicationContext context; /** * Create a new {@link ApplicationStartedEvent} instance. * @param application the current application * @param args the arguments the application is running with * @param context the context that was being created */ public ApplicationStartedEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context) { super(application, args); this.context = context; } /** * Return the application context. * @return the context */ public ConfigurableApplicationContext getApplicationContext() { return this.context; } } ``` `ApplicationStartedEvent`事件將在容器重新整理後但`ApplicationRunner`和`CommandLineRunner`的`run`方法執行前觸發,標誌`Spring`容器已經重新整理,此時容器已經準備完畢了。 > **擴充套件:** 這裡提到了`ApplicationRunner`和`CommandLineRunner`介面有啥作用呢?我們一般會在`Spring`容器重新整理完畢後,此時可能有一些系統引數等靜態資料需要載入,此時我們就可以實現了`ApplicationRunner`或`CommandLineRunner`介面來實現靜態資料的載入。 # 3.4.6 ApplicationReadyEvent ```java // ApplicationReadyEvent.java public class ApplicationReadyEvent extends SpringApplicationEvent { private final ConfigurableApplicationContext context; /** * Create a new {@link ApplicationReadyEvent} instance. * @param application the current application * @param args the arguments the application is running with * @param context the context that was being created */ public ApplicationReadyEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context) { super(application, args); this.context = context; } /** * Return the application context. * @return the context */ public ConfigurableApplicationContext getApplicationContext() { return this.context; } } ``` `ApplicationReadyEvent`事件在呼叫完`ApplicationRunner`和`CommandLineRunner`的`run`方法後觸發,此時標誌`SpringApplication`已經正在執行。 # 3.4.7 ApplicationFailedEvent ```java // ApplicationFailedEvent.java public class ApplicationFailedEvent extends SpringApplicationEvent { private final ConfigurableApplicationContext context; private final Throwable exception; /** * Create a new {@link ApplicationFailedEvent} instance. * @param application the current application * @param args the arguments the application was running with * @param context the context that was being created (maybe null) * @param exception the exception that caused the error */ public ApplicationFailedEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context, Throwable exception) { super(application, args); this.context = context; this.exception = exception; } /** * Return the application context. * @return the context */ public ConfigurableApplicationContext getApplicationContext() { return this.context; } /** * Return the exception that caused the failure. * @return the exception */ public Throwable getException() { return this.exception; } } ``` 可以看到`ApplicationFailedEvent`事件除了多了一個`context`屬性外,還多了一個`Throwable`型別的`exception`屬性用來記錄SpringBoot啟動失敗時的異常。 `ApplicationFailedEvent`事件在SpringBoot啟動失敗時觸發,標誌SpringBoot啟動失敗。 # 4 小結 此篇文章相對簡單,對SpringBoot內建的7種生命週期事件進行了詳細分析。我們還是引用上篇文章的一張圖來回顧一下這些生命週期事件及其用途: ![](https://user-gold-cdn.xitu.io/2020/5/2/171d300d55cc4470?w=796&h=769&f=png&s=378851) # 5 寫在最後 由於有一些小夥伴們建議之前有些原始碼分析文章太長,導致耐心不夠,看不下去,因此,之後的原始碼分析文章如果太長的話,筆者將會考慮拆分為幾篇文章,這樣就比較短小了,比較容易看完,嘿嘿。 **【原始碼筆記】Github地址:** https://github.com/yuanmabiji/Java-SourceCode-Blogs **點贊搞起來,嘿嘿嘿!** --------------------------------------------------- 公眾號【**原始碼筆記**】專注於Java後端系列框架的原始碼分析。 ![](https://user-gold-cdn.xitu.io/2020/3/15/170dd9bb2b5b59de?w=142&h=135&f=png&s