SpringBoot運行流程源碼分析:run方法流程及監(jiān)聽器
SpringBoot運行流程源碼分析
上一章中我們分析了 SpringApplication 類實例化的源代碼,在此過程中完成了基本配置文件的加載和實例化。當 SpringApplication 對象被創(chuàng)建之后, 通過調(diào)用其 run 方法來進行SpringBoot 的啟動和運行,至此正式開啟了 SpringApplication 的生命周期。
本章介紹的內(nèi)容同樣是 Spring Boot 運行的核心流程之一,我們將會圍繞
SpringApplicationRunListeners、ApplicationArguments、ConfigurableEnvironment 以及 應用上下文信息等部分展開講解。
run方法核心流程
在分析和學習整個 run 方法的源代碼及操作之前,我們先通過圖 4-1 所示的流程圖來看一下SpringApplication 調(diào)用的 run 方法處理的核心操作都包含哪些。然后,后面的章節(jié)我們再逐步細化分析每個過程中的源代碼實現(xiàn)。

上面的流程圖可以看出,SpringApplication 在 run 方法中重 點做了以下操作。
.獲取監(jiān)聽器和參數(shù)配置。
.打印 Banner 信息。
.創(chuàng)建并初始化容器。
監(jiān)聽器發(fā)送通知。
當然,除了核心操作,run 方法運行過程中還涉及啟動時長統(tǒng)計、異常報告、啟動日志、異常處理等輔助操作。
對照流程圖,我們再來整體看一下入口 run 方法的源代碼,核心部分的功能已通過注釋的形式進行說明。
public ConfigurableApplicationContext run(String... args) {
//創(chuàng)建 stopwatch 對象, 用于統(tǒng) i 計 run 方法啟動時長
StopWatch stopWatch = new StopWatch();
//啟動統(tǒng)計
stopwatch.start();
ConfigurableApplicationContext context = null;
Collection except ionReporters = new Arraylis
t<>();
//配置 headless 屬性
configureHeadlessProperty();
//獲得 SpringAppl icat ionRunL istener 數(shù)組
//該數(shù)組封裝 FSpringAppl icat ionRunL isteners 對象的 L isteners 中
SpringApplicationRunListeners listeners = getRunListeners(args);
//啟動監(jiān)聽,遍歷 SpringAppl icat ionRunL istener 數(shù)組每個元素,并執(zhí)行
listeners .starting();
try {創(chuàng)建 Appl icat ionArguments 對象
ApplicationArguments applicationArguments = new DefaultApplicationArgum
ents(
args);
//加載屬性配置,包括所有的配置屬性(如: appl icat ion. properties 中和外部的屬
性配置)
Conf igurableEnvironment environment = prepareEnvironment(listeners,
applicationArg
uments);
configureIgnoreBeanInfo( environment);
//打 Banner
Banner printedBanner = printBanner ( environment);
//創(chuàng)建容器
context = createApplicationContext();
//異常報告器
exceptionReporters = getSpringFactoriesInstances(
Spr ingBootExcept ionReporter .class ,
new Class[] { ConfigurableApplicat ionContext.class }, context);
//準備容器,組件對象之間進行關聯(lián)
prepareContext(context, environment, listeners, applicationArguments, p
rintedBanner);
// 初始化容器
refreshContext(context);
//初始化操作之后執(zhí)行,默認實現(xiàn)為空
afterRefresh( context, applicationArguments);
//停止時長統(tǒng)計
stopWatch. stop();
//打印啟動日志
if (this. logStartupInfo) {
new StartupInfoLogger(this . mainApplicat ionClass)
.logStarted(getApplicationLog(), stopwatch);
//通知監(jiān)昕器:容器啟動完成
listeners . started( context);
//調(diào)用 Appl icat ionRunner 和 CommandL ineRunner 的運行方法。
callRunners (context, applicat ionArguments);
} catch (Throwable ex)//異常處理
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
try {
//通知監(jiān)聽器:容器正在運行
listeners . running( context);
} catch (Throwable ex) {
// 異常處理
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
return context;
} 在整體了解了整個 run 方法運行流程及核心代碼后,下面我們針對具體的程進行講解。
SpringApplicationRunListener 監(jiān)聽器
監(jiān)聽器的配置與加載
讓我們忽略 Spring Boot 計 時和統(tǒng)計的輔助功能,直接來看
SpringApplicationRunListeners獲取和使用 SpringApplicationRunL isteners可以理解為一個 SpringApplicationRunListener的容器,它將 SpringApplicationRunListener 的集合以構造方法傳入, 并賦值給其 listeners成員變量,然后提供了針對 listeners 成員變量的各種遍歷操作方法,比如,遍歷集合并調(diào)用對應的 starting、started、 running 等方法。
SpringApplicationRunListeners 的構建很簡單,圖 4-1 中調(diào)用的 getRunListeners 方法也只是調(diào)用了它的構造方法。SpringApplication 中 getRunListeners 方法代碼如下。
private SpringApplicationRunListeners getRunListeners(String[] args) {
//構造 Class 數(shù)組
Class>[] types = new Class>[] { SpringApplication. class, String[].cla
/調(diào)用 SpringAppl icat ionRunL isteners 構造方法
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstan
ces
SpringApplicationRunListener .class, types, this, args));}
SpringApplicationRunListeners 構 造 方 法 的 第 二 個 參 數(shù) 便 是 SpringApplicationRunL
istener 的 集 合 , SpringApplication 中 調(diào) 用 構 造 方 法 時 該 參 數(shù) 是 通 過
getSpringFactoriesInstances 方法獲取的,代碼如下。
private Collection getSpringF actoriesInstances(Class type,
Class>[] parameterTypes, object... args) {
//加 META- TNE/sprina. factori es 中對應監(jiān)聽器的配
并將結果存 F'set 中(去重)
Set names= newLinkedHashSet<>(
個命的能直,
SpringF actoriesl oader. loadFactoryNames(type, classloader));
/文憫化監(jiān)聽器
List instances = createSpringFactories Instances (type, parameterTypes,
classLoader, args, nam
排序
Annotat ionAwareOrderComparator .sort(instances);
eturn instances ; } 通過方法名便可得知,
getSpringFactoriesInstances 是用來獲取 factories 配置文件中的注冊類,并進行實例化操作。
關于通過 SpringFactoriesL oader 獲取 META-INF/spring.factories 中對應的配置,前面章節(jié)已經(jīng)多次提到,這里不再贅述。
SpringApplicationRunListener 的注冊配置位于 spring-boot 項目中的 spring.factories 文件內(nèi),Spring Boot 默認僅有- -個監(jiān)聽器進行了注冊,關于其功能后面會專門講到。
# RunListeners
org. springframework. boot.SpringApplicationRunListener=\
org. springframework. boot. context.event.EventPublishingRunL istener
我們繼續(xù)看實例化監(jiān)聽器的方法 createSpringFactoriesInstances 的源代碼。
private List createSpringFactoriesInstances(Class type
Class>[] parameterType
s, ClassLoader classLoader, Object[] args,
Set<String> names) {
List instances = new ArrayList<>(names.size());
for (String name : names)
Class> instanceClass = ClassUtils . forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
//獲取有參構造器
Constructor> constructor = instanceClass . getDeclaredConstructor(par
ameterTypes);
T instance = (T) BeanUtils. instant iateClass(constructor, args);
instances . add(instance);
return instances;
} 在上面的代碼中,實例化監(jiān)聽器時需要有一-個默認的構造方法, 且構造方法的參數(shù)為Class>[ ] parameterTypes。我們向上追蹤該參數(shù)的來源,會發(fā)現(xiàn)該參數(shù)的值為 Class 數(shù)組 , 數(shù) 組 的 內(nèi) 容 依 次 為 SpringApplication.class 和 String[ ].class 。也 就 是 說 ,SpringApplicationRunL istener 的實現(xiàn)類必須有默認的構造方法,且構造方法的參數(shù)必須依次為 SpringApplication 和 String[ ]類型。
SpringApplicationRunListener 源碼解析
接口
SpringApplicationRunListener 是 SpringApplication 的 run 方法監(jiān)聽器。上節(jié)提到了SpringApplicationRunListener 通過 SpringFactoriesL oader 加載,并且必須聲明一個公共構造函數(shù),該函數(shù)接收 SpringApplication 實例和 String[ ]的參數(shù),而且每次運行都會創(chuàng)建一個新的實例。
SpringApplicationRunListener 提供了-系列的方法,用戶可以通過回調(diào)這些方法,在啟動各個流程時加入指定的邏輯處理。下面我們對照源代碼和注釋來了解一下該接口都定義了哪些待實現(xiàn)的方法及功能。
public interface SpringApplicationRunL istener {
// 當 run 方法第- 次被執(zhí)行時,會被立即調(diào)用,可用于非常早期的初始化工作 default void
starting(){};
//當 environment 準備完成, 在 Appl icationContext 創(chuàng)建之前, 該方法被調(diào)用 default void
environmentPrepared(Conf igurableEnvironment environment) {};
//當 ApplicationContext 構建完成, 資源還未被加載時, 該方法被調(diào)用 default void
contextPrepared(ConfigurableApplicationContext context) {}
// 當 Appl icat ionContext 加 載 完 成 , 未 被 刷 新 之 前 , 該 方 法 被 調(diào) 用 default void
contextLoaded(ConfigurableApplicationContext context) {};
//當 ApplicationContext 刷新并啟動之后, CommandL ineRunner 和 Appl icat ionRunner 未
被調(diào)用之前, 該方法被調(diào)用
default void started(Conf igurableApplicationContext context) {};
//當所有準備工作就緒,run 方法執(zhí)行完成之前, 該方法被調(diào)用
default void running(ConfigurableApplicationContext context) {};
//當應用程序出現(xiàn)錯誤時,該方法被調(diào)用
default void failed(ConfigurableApplicationContext context, Throwable exception) {};
}我們通過源代碼可以看出,
SpringApplicationRunListener 為 run 方法提供了各個運行階段的監(jiān)聽事件處理功能。需要注意的是,該版本中的接口方法定義使用了 Java8 的新特性,方法已采用 default 聲明并實現(xiàn)空方法體,表示這個方法的默認實現(xiàn),子類可以直接調(diào)用該方法,也可以選擇重寫或者不重寫。
圖 4-2 展示了在整個 run 方法的生命周期中
SpringApplicationRunListener 的所有方法所處的位置,該圖可以幫助我們更好地學習 run 方法的運行流程。在前面 run 方法的代碼中已經(jīng)看到相關監(jiān)聽方法被調(diào)用,后續(xù)的源代碼中也將涉及對應方法的調(diào)用,我們可參考此圖以便理解和加深記憶。

實現(xiàn)類
EventPublishingRunListener
EventPublishingRunL istener 是 SpringBoot 中針對
SpringApplicationRunListener 接口的唯內(nèi)建實現(xiàn)EventPublishingRunL istener使用內(nèi)置的SimpleApplicationEventMulticaster來廣播在上下文刷新之前觸發(fā)的事件。
默認情況下,Spring Boot在初始化過程中觸發(fā)的事件也是交由
EventPublishingRunListener來代理實現(xiàn)的。EventPublishingRunListener 的構造方法如下。
public EventPublishingRunListener(SpringApplication application, Stringa
rgs) {
this.application = application;
this.args = args;
//創(chuàng)建 SimpleAppl icat ionEventMulticaster/播器
this . initialMulticaster = new SimpleApplicationEventMulticaster();
//遍歷 Appl icat ionL istener 并關聯(lián) S impleAppl icat ionEventMulticaster
for (ApplicationListener> listener : application. getListeners()) {
this. initialMulticaster . addApplicationListener(listener);
}通過源代碼可以看出,該類的構造方法符合
SpringApplicationRunListener 所需的構造方法參數(shù)要求,該方法依次傳遞了 SpringApplication 和 String[ ]類型。在構造方法中初始化了該類的 3 個成員變量。
-application :類 型為 SpringApplication ,是當前運行的 SpringApplication 實例。
-args:啟動程序時的命令參數(shù)。
-initialMulticaster:類 型為
SimpleApplicationEventMulticaster,事件廣播器。
Spring Boot 完成基本的初始化之后,會遍歷 SpringApplication 的所有 ApplicationListener實 例 , 并 將 它 們 與
SimpleApplicationEventMulticaster 進 行 關 聯(lián) , 方 便SimpleApplicationEvent-Multicaster 后續(xù)將事件傳遞給所有的監(jiān)聽器。
EventPublishingRunListener 針對不同的事件提供了不同的處理方法,但它們的處理流程基本相同。

下面我們根據(jù)圖 4-3 所示的流程圖梳理一下 整個事件的流程。
.程序啟動到某個步驟后,調(diào)用
EventPublishingRunListener 的某個方法。
EventPublishingRunListener 的具體方法將 application 參數(shù)和 args 參數(shù)封裝到對應的事件中。這里的事件均為 SpringApplicationEvent 的實現(xiàn)類。
.通過成員變量 initialMulticaster 的 multicastEvent 方法對事件進行廣播,或通過該方法的
ConfigurableApplicationContext 參數(shù)的 publishEvent 方法來對事件進行發(fā)布。
.對應的 ApplicationListener 被觸發(fā),執(zhí)行相應的業(yè)務邏輯。
下面是 starting 方法的源代碼,可對照上述流程進行理解。該方法其他功能類似,代碼不再展示。
public void starting() {
this. initialMulticaster . multicastEvent(
new ApplicationStartingEvent (this. application, this.args));}在上述源代碼中你是否發(fā)現(xiàn)-個問題,某些方法是通過 initialMulticaster 的 multicastEvent進行事件的廣播,某些方法是通過 context 參數(shù)的 publishEvent 方法來進行發(fā)布的。這是為什么呢?在解決這個疑問之前,我們先看一個比較特殊的方法 contextL oaded 的源代碼。
public void contextLoaded( ConfigurableApplicationContext context) {
//遍歷 application 中的所有監(jiān)聽器實現(xiàn)類
for (ApplicationL istener> listener : this . application. getL isteners()) {
//如果為 Appl icationContextAware,則將上:下文信息設置到該監(jiān)聽器內(nèi)
if (listener instanceof Applicat ionContextAware) {
((ApplicationContextAware) listener). setApplicationContext( context);//將 application 中的監(jiān)聽器實現(xiàn)類全部添加到上下文中
context . addApplicationL istener(listener);
// / "播事件 Appl icationPreparedEvent
this. initialMulticaster . multicastEvent (
new ApplicationPreparedEvent(this.application, this.args, context));
}contextLoaded 方法在發(fā)布事件之前做了兩件事:第一,遍歷 application 的所有監(jiān)聽器實現(xiàn)類,如果該實現(xiàn)類還實現(xiàn)了 ApplicationContextAware 接口,則將上下文信息設置到該監(jiān)聽器內(nèi);第二,將 application 中的監(jiān)聽器實現(xiàn)類全部添加到上下文中。最后一步才是調(diào)用事件廣播。
也正是這個方法形成了不同事件廣播形式的分水嶺,在此方法之前執(zhí)行的事件廣播都是通過multicastEvent 來進行的,而該方法之后的方法則均采用 publishEvent 來執(zhí)行。這是因為只有到了 contextL oaded 方法之后,上下文才算初始化完成,才可通過它的 publishEvent 方法來進行事件的發(fā)布。
自定義
SpringApplicationRunListener
上面我們一起學習了
SpringApplicationRunListener 的基本功能及實現(xiàn)類的源代碼,現(xiàn)在我們自定義-個 SpringApplicationRunListener 的實現(xiàn)類。通過在該實現(xiàn)類中回調(diào)方法來處理自己的業(yè)務邏輯。
自定義實現(xiàn)類比較簡單,可像通常實現(xiàn)一個接口一樣,先創(chuàng)建類 MyApplicationRunListener,實現(xiàn)接口
SpringApplicationRunListener 及其方法。然后在對應的方法內(nèi)實現(xiàn)自己的業(yè)務邏輯,以下示例代碼中只簡單打印方法名稱。與普通接口實現(xiàn)唯一不同的是,這里需要指定一-個參數(shù)依次為 SpringApplication 和 String[ ]的構造方法,不然在使用時會直接報錯。
public class MyApplicationRunListener implements SpringApplicationRunListen
er {
public MyApplicationRunListener ( SpringApplication application, String[]
args){
System. out . println("MyApplicationRunListener constructed function");
@Override
public void starting() {
System. out . println("starting...");
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System. out. println(" environmentPrepared...");
//在此省略掉其他方法的實現(xiàn) }當定義好實現(xiàn)類之后,像注冊其他監(jiān)聽器一樣, 程序在 spring.factories 中進行注冊配置。如果項目中沒有 spring.factories 文件,也可在 resources 目錄下先創(chuàng)建 META-INF 目錄,然后在該目錄下創(chuàng)建文件 sprig.factories。
spring.factories 中配置格式如下。
# Run Listeners
org. springframework. boot . SpringApplicationRunListener=\
com. secbro2. learn. listener . MyApplicationRunListener啟動 Spring Boot 項目,你會發(fā)現(xiàn)在不同階段打印出不同的日志,這說明該實現(xiàn)類的方法已經(jīng)被調(diào)用。
本文給大家講解的內(nèi)容是run方法核心流程 SpringApplicationRunListener 監(jiān)聽器
下篇文章給大家講解的是初始化ApplicationArguments和初始化 ConfigurableEnvironment;
覺得文章不錯的朋友可以轉(zhuǎn)發(fā)此文關注小編;
感謝大家的支持!
本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學習更多的話可以到微信公眾號里找我,我等你哦。
