一、執行原理:
每個Spring Boot項目都有一個主程序啟動類,在主程序啟動類中有一個啟動項目的main()方法, 在該方法中通過執行SpringApplication.run()即可啟動整個Spring Boot程序。
Q:
那么SpringApplication.run()方法到底是如何做到啟動Spring Boot項目的呢?
@SpringBootApplication //能夠掃描Spring組件並自動配置SpringBoot
public class Springboot01DemoApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01DemoApplication.class, args);
}
}
上述是一個SpringBoot的啟動類,進入SpringApplication.run()方法

如圖所示,進入了run方法后,緊接着,調用了重載方法,重載方法做了兩件事:
- 實例化SpringApplication對象
- 調用run方法
1. 實例化SpringApplication對象
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//......設置了一些參數....這里省略,下面是重點
//......設置了一些參數....這里省略,下面是重點
//......設置了一些參數....這里省略,下面是重點
//項目啟動類 SpringbootDemoApplication.class設置為屬性存儲起來
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//設置應用類型是SERVLET應用(Spring 5之前的傳統MVC應用)還是REACTIVE應用(Spring 5開始出現的WebFlux交互式應用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 設置初始化器(Initializer),最后會調用這些初始化器
//所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,在Spring上下文被刷新之前進行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 設置監聽器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 初始化 mainApplicationClass 屬性:用於推斷並設置項目main()方法啟動的主程序啟動類
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringApplication的構造方法中,首先設置了一些參數,然后做了5件事:
1.1 項目啟動類 SpringbootDemoApplication.class設置為屬性存儲起來
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
給這個成員變量賦值,把傳入的primarySources進行轉換,然后賦值,這個primarySources就是我們Springboot啟動類的Main方法中傳入的:

1.2 設置應用類型是SERVLET應用(Spring 5之前的傳統MVC應用)還是REACTIVE應用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
判斷當前的web應用類型是servlet應用還是reactive應用,那么如何判斷的? 進入.deduceFromClasspath()方法:

- 首先判斷類路徑下Reactive相關的class是否存在,如果存在就說明當前應用是內嵌的 Reactive Web 應用。例如說,Spring Webflux 。
- 判斷類路徑下是否存在Servlet類型的類。如果不存在,則返回
NONE,表示當前應用是非內嵌的 Web 應用。 - 否則,表示當前應用是內嵌的 Servlet Web 應用。例如說,Spring MVC 。
1.3 設置初始化器(Initializer),最后會調用這些初始化器
所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,在Spring上下文被刷新之前進行初始化的操作.
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
這里傳入了一個ApplicationContextInitializer.class
進入getSpringFactoriesInstances()方法(下圖如果看不清請右鍵另存為):

這段代碼主要做了如下幾件事:
-
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
這里的type就是剛才傳入的,ApplicationContextInitializer.class -
loadFactoryNames調用了loadSpringFactories方法 -
loadSpringFactories方法做了如下的事:Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap();判斷
classLoader是否為空,如果不為空加載META-INF下的spring.factories,如上圖所示,根據傳入的參數值(ApplicationContextInitializer.class)的類型,在spring.factories中進行查找,根據當前傳入的類型找到兩個類,這兩個類就是初始化器:org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
得到這兩個類后,把它們存入set去重,然后進行實例化,然后排序,最終返回,到此初始化器已經設置完成了。然后存入List<ApplicationContextInitializer<?>> initializers,等待之后使用

1.4 設置監聽器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
和1.3同理,也是通過調用getSpringFactoriesInstances,只不過傳遞的參數發生了改變。變成了ApplicationListener.class ,所以它就是在spring.factories中根據ApplicationListener.class找,然后實例化,然后返回存入Listeners中。
1.5 初始化 mainApplicationClass 屬性
用於推斷並設置項目main()方法啟動的主程序啟動類
this.mainApplicationClass = deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
try {
// 獲得當前 StackTraceElement 數組
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 判斷哪個執行了 main 方法
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
判斷哪個類執行了main方法,然后返回。
1.6 總結
實例化SpringApplication對象做了哪些事?
- 項目啟動類 SpringbootDemoApplication.class設置為屬性存儲起來
- 設置應用類型是SERVLET應用(Spring 5之前的傳統MVC應用)還是REACTIVE應用(Spring 5開始出現的WebFlux交互式應用)
- 設置初始化器(Initializer),最后會調用這些初始化器
- 設置監聽器(Listener)
- 初始化 mainApplicationClass 屬性:用於推斷並設置項目main()方法啟動的主程序啟動類
2. 調用run方法
回憶一下,在SpringBoot啟動類的Main方法中,執行了SpringApplication.run(Main方法所在的當前類.class, args);,這個方法主要做了兩件事:
- 實例化SpringApplication對象 (已上述)
- 調用run方法
進入run方法:

run方法大體上做了9件比較重要的事。
2.1 獲取並啟動監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
//args是啟動Spring應用的命令行參數,該參數可以在Spring應用中被訪問。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

它其實還是通過getSpringFactoriesInstances()這個方法來獲取,這個方法已經很熟悉了, 1.3,1.4都使用到了,不再贅述。
那么本步驟就是通過getSpringFactoriesInstances()拿到了一個SpringApplicationRunListeners類型的監聽器,然后調用.starting()啟動。
2.2 項目運行環境Environment的預配置
創建並配置當前SpringBoot應用將要使用的Environment,並遍歷調用所有的SpringApplicationRunListener的environmentPrepared()方法
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 准備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術字體
Banner printedBanner = printBanner(environment);
進入prepareEnvironment()方法:

- 查詢environment,有就返回,沒有的話創建后返回。
- 配置環境
- PropertySources:加載執行的配置文件
- Profiles:多環境配置,針對不同的環境,加載不同的配置
- listeners環境准備(就是廣播ApplicationEnvironmentPreparedEvent事件)。
- 將創建的環境綁定到SpringApplication對象上
- 是否是web環境,如果不是就轉換為標准環境
- 配置PropertySources對它自己的遞歸依賴
- 返回
此時已經拿到了ConfigurableEnvironment 環境對象,然后執行configureIgnoreBeanInfo(environment),使其生效。
2.3 創建Spring容器
context = createApplicationContext();
// 獲得異常報告器 SpringBootExceptionReporter 數組
//這一步的邏輯和實例化初始化器和監聽器的一樣,
// 都是通過調用 getSpringFactoriesInstances 方法來獲取配置的異常類名稱並實例化所有的異常處理類。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);

根據 webApplicationType 類型,獲得 ApplicationContext 類型,這里創建容器的類型 還是根據webApplicationType進行判斷的,該類型為SERVLET類型,所以會通過反射裝載對應的字節碼,也就是AnnotationConfigServletWebServerApplicationContext。
然后通過getSpringFactoriesInstances()獲得異常報告器。
2.4 Spring容器前置處理
這一步主要是在容器刷新之前的准備動作。包含一個非常關鍵的操作:將啟動類注入容器,為后續開啟自動化配置奠定基礎。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);

這塊會對整個上下文進行一個預處理,比如觸發監聽器的響應事件、加載資源、設置上下文環境等等。
2.5 刷新容器
refreshContext(context);

- IOC容器初始化
- 向JVM運行時注冊一個關機鈎子(函數),在JVM關機時關閉這個上下文,除非它當時已經關閉。(如果jvm變關閉了,當前上下文對象也可以被關閉了)
//TODO refresh方法在springioc章節中會有詳細說明(挖個坑- - )。
2.6 Spring容器后置處理
afterRefresh(context, applicationArguments);

擴展接口,設計模式中的模板方法,默認為空實現。
如果有自定義需求,可以重寫該方法。比如打印一些啟動結束log,或者一些其它后置處理。
2.7 發出結束執行的事件通知
listeners.started(context);

2.8 執行Runners運行器
callRunners(context, applicationArguments);

用於調用項目中自定義的執行器XxxRunner類,使得在項目啟動完成后立即執行一些特定程序。
Runner 運行器用於在服務啟動時進行一些業務初始化操作,這些操作只在服務啟動后執行一次。
Spring Boot提供了ApplicationRunner和CommandLineRunner兩種服務接口
2.9 發布應用上下文就緒事件
listeners.running(context);
表示在前面一切初始化啟動都沒有問題的情況下,使用運行監聽器SpringApplicationRunListener持續運行配置好的應用上下文ApplicationContext.
這樣整個Spring Boot項目就正式啟動完成了。
2.10 返回容器
return context;
完成~
總結:
- 獲取並啟動監聽器
- 項目運行環境Environment的預配置
- 創建Spring容器
- Spring容器前置處理
- 刷新容器
- Spring容器后置處理
- 發出結束執行的事件通知
- 執行Runners運行器
- 發布應用上下文就緒事件
- 返回容器
