一、【徹底精通Spring Boot2原始碼】 SpringApplication.run() 啟動流程原始碼徹底剖析
本文是對spring boot2 啟動整個流程進行原始碼解析,也是我發表springboot系列的第一篇文章,解析的過程我會一行一行解讀,網上很多的解析案例都是貼一段原始碼,然後說幾句概況性的語句,看了之後也是丈二和尚 摸不到頭腦,既然解析原始碼,我們就儘量徹底理解每一行是做什麼的,有什麼作用。
下面我們來看原始碼,走進啟動模式;
SpringApplication.run(BootApplication.class, args);
啟動類開始執行;
new SpringApplication(primarySources).run(args); public SpringApplication(Class<?>... primarySources) { this(null, primarySources); }
先走進SpringApplication的構造方法,new一個SpringApplication例項,然後執行run方法。run方法執行結束之後整個啟動流程就是走完了。
1、SpringApplication 的建構函式,建立SpringApplication物件,一下是建構函式實現的具體初始化過程。
//使用的資源載入器
//主要的bean資源 primarySources【在這裡是啟動類所在的.class】,不能為null,如果為null,拋異常
/**
* primarySources 是Set<Class<?>> 型別,把啟動類(我這裡是定義為BootApplication)
* .class (Class<?> 該類的例項陣列轉化成list,放在LinkedHashSet集合中)
*/
/**
* deduceWebApplicationType() = SERVER
* 應用程式應該作為基於servlet的web應用程式執行,並且應該啟動一個
* 嵌入式servlet web伺服器。
*/
/**
* 使用類載入機制,和發射實現對ApplicationContextInitializer 介面熟悉的初始化。
* ApplicationContextInitializer是Spring IOC一個回撥介面,它會在ConfigurableApplicationContext的refresh()
* 方法呼叫之前被呼叫,做一些容器的初始化工作。每一個initailizer都是一個實現了ApplicationContextInitializer介面的例項。
* springboot中一共有6個實現類,後面遇到再分析各個實現類初始化的作用。
*/
/** 使用類載入機制,和發射實現對ApplicationListener(事件監聽容器) 介面熟悉的初始化。
當用Spring ApplicationContext註冊時,事件將相應地過濾,並呼叫偵聽器以匹配事件物件。
*/
/** 指定main函式啟動所在的類,即啟動類BootApplication.class
2、啟動執行Spring應用程式,建立並重新整理一個新應用application程式
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
/**
StopWatch: 簡單的秒錶,允許定時的一些任務,公開每個指定任務的總執行時間和執行時間。
這個物件的設計不是執行緒安全的,沒有使用同步。不會作為生產環境中使用,二此處因為啟動
SpringApplication是在單執行緒環境下,使用安全。
*/
/** 設定當前啟動的時間為系統時間startTimeMillis = System.currentTimeMillis();*/
/** 系統設定headless模式,
(是一種系統配置,其中顯示裝置、鍵盤或滑鼠缺乏。聽起來出乎意料,但實際上您可以在這種模式下執行不同的操作,即 使是使用 圖形資料。)
想進一步瞭解的訪問(https://www.oracle.com/technetwork/articles/javase/headless-136834.html)
*/
/** 通過類載入機制和反射建立SpringApplicationRunListeners例項SpringApplicationRunListeners:在建立和更新ApplicationContext 方法前後分別觸發回撥的形式呼叫了listeners物件的started方法和finished....等等方法, 並在建立和重新整理ApplicationContext的不同階段,呼叫listeners的相應方法以執行操作。所以,SpringApplicationRunListeners實際上就是在SpringApplication 物件的run方法執行的不同階段,去監聽回撥一些操作,並且這些操作是可配置的。 通過spring.factories中發現SpringApplicationRunListeners 載入的是org.springframework.boot.context.event.EventPublishingRunListener是 SpringApplicationRunListener的實現類.(SpringApplicationRunListeners的內部儲存了SpringApplicationRunListener的集合,提供了SpringApplicationRunListener一樣方 法,方便統一遍歷呼叫所有SpringApplicationRunListener。)
*/
/** 開始監聽指定的一些event事件,檢視原始碼springboot中應該是有45處會觸發回撥*/
/** 提供對用於執行SpringApplication的引數的訪問。取預設實現*/
/**
構建容器環境
*/
/**
對環境中一些bean忽略配置
*/
/**
* 日誌控制檯列印設定
* /
/**
建立容器
*/
/**
追蹤原始碼prepareContext()進去我們可以發現容器準備階段做了下面的事情:
容器設定配置環境,並且監聽容器,初始化容器,記錄啟動日誌,
將給定的singleton物件新增到此工廠的singleton快取中。
將bean載入到應用程式上下文中。
*/
/**
1、同步重新整理,對上下文的bean工廠包括子類的重新整理準備使用,初始化此上下文的訊息源,註冊攔截bean的處理器,
檢查偵聽器bean並註冊它們,例項化所有剩餘的(非延遲-init)單例。
2、非同步開啟一個同步執行緒去時時監控容器是否被關閉,當關閉此應用程式上下文,銷燬其bean工廠中的所有bean。
如果我們註冊了一個jvm shutdown hook,那就不需要單獨去開啟一個執行緒去監控了
*/
/** 重新整理容器之後無任何操作,裡面是空的方法*/
/**
stopwatch 的作用就是記錄啟動消耗的時間,和開始啟動的時間等資訊記錄下來
*/
/** 啟動監聽容器中所有的事件 :
上下文已經重新整理,應用程式已經啟動,但是沒有呼叫CommandLineRunners和applicationrunner。
*/
/**
啟動監聽:
當應用程式上下文被重新整理,所有CommandLineRunners和applicationrunner都被呼叫時,在執行方法完成之前立即呼叫。
*/
............................................
上面的原始碼中多處都是內部是用了下面原始碼中的幾個方法,其實本質就是解析載入spring-boot-2.0.3.RELEASE.jar/META-INF/spring.factories
中的配置,來建立初始化我們需要的Application context/listeners/Loaders。
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
spring-boot-2.0.3.RELEASE.jar/META-INF/spring.factories:
原始碼如下:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers\
........
springboot run啟動流程就是這樣!看完差不多也知道springboot啟動成功之後做了哪些!