Spring Boot 2.x 啓動全過程源碼分析(全)

上篇《Spring Boot 2.x 啟動全過程源碼分析(一)入口類剖析》我們分析了 Spring Boot 入口類 SpringApplication 的源碼,並知道了其構造原理,這篇我們繼續往下面分析其核心 run 方法。

SpringApplication 實例 run 方法運行過程


Spring Boot 2.x 啟動全過程源碼分析(全)

上面分析了 SpringApplication 實例對象構造方法初始化過程,下面繼續來看下這個 SpringApplication 對象的 run 方法的源碼和運行流程。

public ConfigurableApplicationContext run(String... args) {
// 1、創建並啟動計時監控類
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2、初始化應用上下文和異常報告集合
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();
// 3、設置系統屬性 `java.awt.headless` 的值,默認值為:true
configureHeadlessProperty();
// 4、創建所有 Spring 運行監聽器併發布應用啟動事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 5、初始化默認應用參數類
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 6、根據運行監聽器和應用參數來準備 Spring 環境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 7、創建 Banner 打印類
Banner printedBanner = printBanner(environment);
// 8、創建應用上下文
context = createApplicationContext();
// 9、準備異常報告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 10、準備應用上下文

prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 11、刷新應用上下文
refreshContext(context);
// 12、應用上下文刷新後置處理
afterRefresh(context, applicationArguments);
// 13、停止計時監控類
stopWatch.stop();
// 14、輸出日誌記錄執行主類名、時間信息
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 15、發佈應用上下文啟動完成事件
listeners.started(context);
// 16、執行所有 Runner 運行器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 17、發佈應用上下文就緒事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
// 18、返回應用上下文
return context;
}

所以,我們可以按以下幾步來分解 run 方法的啟動過程。

1、創建並啟動計時監控類

StopWatch stopWatch = new StopWatch();
stopWatch.start();

來看下這個計時監控類 StopWatch 的相關源碼:

public void start() throws IllegalStateException {
start("");
}
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
}
this.currentTaskName = taskName;
this.startTimeMillis = System.currentTimeMillis();
}

首先記錄了當前任務的名稱,默認為空字符串,然後記錄當前 Spring Boot 應用啟動的開始時間。

2、初始化應用上下文和異常報告集合

ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();


3、設置系統屬性 `java.awt.headless` 的值

configureHeadlessProperty();

設置該默認值為:true,Java.awt.headless = true 有什麼作用?

對於一個 Java 服務器來說經常要處理一些圖形元素,例如地圖的創建或者圖形和圖表等。這些API基本上總是需要運行一個X-server以便能使用AWT(Abstract Window Toolkit,抽象窗口工具集)。然而運行一個不必要的 X-server 並不是一種好的管理方式。有時你甚至不能運行 X-server,因此最好的方案是運行 headless 服務器,來進行簡單的圖像處理。參考:www.cnblogs.com/princessd8251/p/4000016.html

4、創建所有 Spring 運行監聽器併發布應用啟動事件

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

來看下創建 Spring 運行監聽器相關的源碼:

private SpringApplicationRunListeners getRunListeners(String[] args) {
Class>[] types = new Class>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
SpringApplicationRunListeners(Log log,
Collection extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}

創建邏輯和之前實例化初始化器和監聽器的一樣,一樣調用的是 getSpringFactoriesInstances 方法來獲取配置的監聽器名稱並實例化所有的類。

SpringApplicationRunListener 所有監聽器配置在 spring-boot-2.0.3.RELEASE.jar!/META-INF/spring.factories 這個配置文件裡面。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

5、初始化默認應用參數類

ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);


6、根據運行監聽器和應用參數來準備 Spring 環境

ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);

下面我們主要來看下準備環境的 prepareEnvironment 源碼:

private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 6.1) 獲取(或者創建)應用環境
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 6.2) 配置應用環境
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);

}
ConfigurationPropertySources.attach(environment);
return environment;
}

6.1) 獲取(或者創建)應用環境

private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
if (this.webApplicationType == WebApplicationType.SERVLET) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}

這裡分為標準 Servlet 環境和標準環境。

6.2) 配置應用環境

protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}

這裡分為以下兩步來配置應用環境。

  • 配置 property sources
  • 配置 Profiles

這裡主要處理所有 property sources 配置和 profiles 配置。

7、創建 Banner 打印類

Banner printedBanner = printBanner(environment);

這是用來打印 Banner 的處理類,這個沒什麼好說的。

8、創建應用上下文

context = createApplicationContext();

來看下 createApplicationContext() 方法的源碼:

protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

其實就是根據不同的應用類型初始化不同的上下文應用類。

9、準備異常報告器

exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);

邏輯和之前實例化初始化器和監聽器的一樣,一樣調用的是 getSpringFactoriesInstances 方法來獲取配置的異常類名稱並實例化所有的異常處理類。

該異常報告處理類配置在 spring-boot-2.0.3.RELEASE.jar!/META-INF/spring.factories 這個配置文件裡面。

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

10、準備應用上下文

prepareContext(context, environment, listeners, applicationArguments,
printedBanner);

來看下 prepareContext() 方法的源碼:

private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 10.1)綁定環境到上下文
context.setEnvironment(environment);
// 10.2)配置上下文的 bean 生成器及資源加載器
postProcessApplicationContext(context);
// 10.3)為上下文應用所有初始化器

applyInitializers(context);
// 10.4)觸發所有 SpringApplicationRunListener 監聽器的 contextPrepared 事件方法
listeners.contextPrepared(context);
// 10.5)記錄啟動日誌
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 10.6)註冊兩個特殊的單例bean
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// 10.7)加載所有資源
Set sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// 10.8)觸發所有 SpringApplicationRunListener 監聽器的 contextLoaded 事件方法
listeners.contextLoaded(context);
}

11、刷新應用上下文

refreshContext(context);

這個主要是刷新 Spring 的應用上下文,源碼如下,不詳細說明。

private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.

}
}
}

12、應用上下文刷新後置處理

afterRefresh(context, applicationArguments);

看了下這個方法的源碼是空的,目前可以做一些自定義的後置處理操作。

/**
* Called after the context has been refreshed.
* @param context the application context
* @param args the application arguments
*/
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}

13、停止計時監控類

stopWatch.stop();
public void stop() throws IllegalStateException {
if (this.currentTaskName == null) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
}
long lastTime = System.currentTimeMillis() - this.startTimeMillis;
this.totalTimeMillis += lastTime;
this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
if (this.keepTaskList) {
this.taskList.add(this.lastTaskInfo);
}
++this.taskCount;
this.currentTaskName = null;
}

計時監聽器停止,並統計一些任務執行信息。

14、輸出日誌記錄執行主類名、時間信息

if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}

15、發佈應用上下文啟動完成事件

listeners.started(context);

觸發所有 SpringApplicationRunListener 監聽器的 started 事件方法。

16、執行所有 Runner 運行器

callRunners(context, applicationArguments);
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}

執行所有 ApplicationRunner 和 CommandLineRunner 這兩種運行器,不詳細展開了。

17、發佈應用上下文就緒事件

listeners.running(context);

觸發所有 SpringApplicationRunListener 監聽器的 running 事件方法。

18、返回應用上下文

return context;


總結

Spring Boot 的啟動全過程源碼分析至此,分析 Spring 源碼真是一個痛苦的過程,希望能給大家提供一點參考和思路,也希望能給正在 Spring Boot 學習路上的朋友一點收穫。


分享到:


相關文章: