SpringBoot系統學習 - 啟動篇
1.前言
到這裡,我們對springBoot對常用的工具整合都有一定的瞭解了,那我們是否想過:springboot啟動的過程都幹了些啥事情啊? 好好想一想?Bean注入容器,配置注入…?
2.啟動流程示意圖
SpringBoot將spring應用的啟動流程進行了一個“模板化”的操作,所以我們才能通過SpringApplication.run(XXX.class, args)的方式來進行一站式的啟動。其內部邏輯也是個較複雜的過程,下文將對執行流程進行闡述。本流程參考的SpringBoot版本為1.4.3.RELEASE。
先有個印象就好。
1)SpringApplicationRunListener
SpringApplicationRunListener是SpringBoot執行過程中,不同執行時間點時間通知的監聽者,一般來說也沒有必要自己實現一個SpringApplicationRunListener,即使是SpringBoot預設也只實現了一個org.springframework.boot.context.event.Event
PublishingRunListener。通過這個類,在SpringBoot啟動時,在不同的時間點發布不同的應用事件型別ApplicationEvent。
SpringBoot初始化時載入的ApplicationListener如果對這些事件感興趣,則可以接收並處理。
public interface SpringApplicationRunListener {
void started();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void finished(ConfigurableApplicationContext context, Throwable exception);
}
2) ApplicationContextInitializer
通過這個類,可以在ApplicationContext呼叫refresh()方法前,對ApplicationContext物件做進一步的設定或者處理。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C applicationContext);
}
3) ApplicationRunner和CommandLineRunner
需要在容器啟動的時候執行一些內容。比如讀取配置檔案,資料庫連線之類的。SpringBoot給我們提供了兩個介面來幫助我們實現這種需求。這兩個介面分別為CommandLineRunner和ApplicationRunner。他們的執行時機為容器啟動完成的時候。
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
3.執行流程
new SpringApplication(primarySources)幹了那些事情呢?
共幹了4件事:
1.推斷應用型別是Standard還是Web
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null)) {
return WebApplicationType.REACTIVE;
} else {
String[] var1 = WEB_ENVIRONMENT_CLASSES;
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String className = var1[var3];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
}
可能會出現三種結果:
1) WebApplicationType.REACTIVE - 當類路徑中存在REACTIVE_WEB_ENVIRONMENT_CLASS並且不存在MVC_
WEB_ENVIRONMENT_CLASS時
2) WebApplicationType.NONE - 也就是非Web型應用(Standard型),此時類路徑中不包含WEB_ENVIRONMENT_CLASSES中定義的任何一個類時
3) WebApplicationType.SERVLET - 類路徑中包含了WEB_ENVIRONMENT_CLASSES中定義的所有型別時
2.設定初始化器(Initializer)
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 這裡的入參type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 使用Set儲存names來避免重複元素
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根據names來進行例項化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 對例項進行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
這裡面首先會根據入參type讀取所有的names(是一個String集合),然後根據這個集合來完成對應的例項化操作:
從類路徑的META-INF/spring.factories處讀取相應配置檔案,然後進行遍歷,讀取配置檔案中Key為:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure這個包為例,它的META-INF/spring.factories部分定義如下所示:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
初始化步驟很直觀,沒什麼好說的,類載入,確認被載入的類確實是org.springframework.context.ApplicationContextInitializer的子類,然後就是得到構造器進行初始化,最後放入到例項列表中。
因此,所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,這個介面是這樣定義的:
根據類文件,這個介面的主要功能是:
在Spring上下文被重新整理之前進行初始化的操作。典型地比如在Web應用中,註冊Property Sources或者是啟用Profiles。Property Sources比較好理解,就是配置檔案。Profiles是Spring為了在不同環境下(如DEV,TEST,PRODUCTION等),載入不同的配置項而抽象出來的一個實體。
3.設定監聽器(Listener)
// 這裡的入參type是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<? extends 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<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
可以發現,這個載入相應的類名,然後完成例項化的過程和上面在設定初始化器時如出一轍,同樣,還是以spring-boot-autoconfigure這個包中的spring.factories為例,看看相應的Key-Value:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
ApplicationListener介面,它是Spring框架中一個相當基礎的介面了,程式碼如下:
這個介面基於JDK中的EventListener介面,實現了觀察者模式。對於Spring框架的觀察者模式實現,它限定感興趣的事件型別需要是ApplicationEvent型別的子類,而這個類同樣是繼承自JDK中的EventObject類。
4) 推斷應用入口類
至此,對於SpringApplication例項的初始化過程就結束了。
構造完了,我們執行run方法咯
SpringApplication.run方法
// 執行run方法
public ConfigurableApplicationContext run(String... args) {
// 計時工具
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 設定java.awt.headless系統屬性為true - 沒有圖形化介面
configureHeadlessProperty();
// KEY 1 - 獲取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
// 發出開始執行的事件
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// KEY 2 - 根據SpringApplicationRunListeners以及引數來準備環境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 準備Banner列印器 - 就是啟動Spring Boot的時候列印在console上的ASCII藝術字型
Banner printedBanner = printBanner(environment);
// KEY 3 - 建立Spring上下文
context = createApplicationContext();
// 準備異常報告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// KEY 4 - Spring上下文前置處理
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// KEY 5 - Spring上下文重新整理
refreshContext(context);
// KEY 6 - Spring上下文後置處理
afterRefresh(context, applicationArguments);
// 發出結束執行的事件
listeners.finished(context, null);
// 停止計時器
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
}
這個run方法包含的內容也是有點多的,根據上面列舉出的關鍵步驟逐個進行分析:
1) 第一步 - 獲取所謂的run listeners:
2)第二步 - 根據SpringApplicationRunListeners以及引數來準備環境
3)第三步 - 建立Spring上下文
4) 第四步 - Spring上下文前置處理
5) 第五步 - Spring上下文重新整理
6) 第六步 - Spring上下文後置處理
1) 第一步 - 獲取所謂的run listeners:
這裡仍然利用了getSpringFactoriesInstances方法來獲取例項:所以這裡還是故技重施,從META-INF/spring.factories中讀取Key為org.springframework.boot.SpringApplicationRunListener的Values:
比如在spring-boot包中的定義的spring.factories:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
2)第二步 - 根據SpringApplicationRunListeners以及引數來準備環境
對於Web應用而言,得到的environment變數是一個StandardServletEnvironment的例項。得到例項後,會呼叫前面RunListeners中的environmentPrepared方法:
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
在這裡,定義的廣播器就派上用場了,它會釋出一個ApplicationEnvironmentPreparedEvent事件。
那麼有釋出就有監聽,在構建SpringApplication例項的時候不是初始化過一些ApplicationListeners嘛,其中的Listener就可能會監聽ApplicationEnvironmentPreparedEvent事件,然後進行相應處理。
**3) 第三步 - 建立Spring上下文
對於我們的Web應用,上下文型別就是DEFAULT_WEB_CONTEXT_CLASS。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
// WEB應用的上下文型別
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
4) 第四步 - Spring上下文前置處理
配置Bean生成器以及資源載入器(如果它們非空)
呼叫初始化器:建立SpringApplication例項時設定的初始化器了,依次對它們進行遍歷,並呼叫initialize方法。
5) 第五步 - Spring上下文重新整理
註冊關閉容器時的鉤子函式的預設實現是在AbstractApplicationContext類中:
如果沒有提供自定義的shutdownHook,那麼會生成一個預設的,並新增到Runtime中。預設行為就是呼叫它的doClose方法,完成一些容器銷燬時的清理工作。
6) 第六步 - Spring上下文後置處理
所謂的後置操作,就是在容器完成重新整理後,依次呼叫註冊的Runners。Runners可以是兩個介面的實現類:
org.springframework.boot.ApplicationRunner
org.springframework.boot.CommandLineRunner
至此,SpringApplication的run方法就分析完畢了。
總結
本文分析了Spring Boot啟動時的關鍵步驟,主要包含以下兩個方面:
SpringApplication例項的構建過程
其中主要涉及到了初始化器(Initializer)以及監聽器(Listener)這兩大概念,它們都通過META-INF/spring.factories完成定義。
SpringApplication例項run方法的執行過程
其中主要有一個SpringApplicationRunListeners的概念,它作為Spring Boot容器初始化時各階段事件的中轉器,將事件派發給感興趣的Listeners(在SpringApplication例項的構建過程中得到的)。這些階段性事件將容器的初始化過程給構造起來,提供了比較強大的可擴充套件性。
如果從可擴充套件性的角度出發,應用開發者可以在Spring Boot容器的啟動階段,擴充套件哪些內容呢:
初始化器(Initializer)
監聽器(Listener)
容器重新整理後置Runners(ApplicationRunner或者CommandLineRunner介面的實現類)
啟動期間在Console列印Banner的具體實現類
相關文章
- SpringBoot系統學習 - 配置篇Spring Boot
- SpringBoot原始碼學習3——SpringBoot啟動流程Spring Boot原始碼
- 《springboot學習篇4》Spring Boot
- 推薦系統實踐學習系列(三)推薦系統冷啟動問題
- 學習java註解,初試啟動springboot專案JavaSpring Boot
- ucore作業系統學習筆記(一) ucore lab1系統啟動流程分析作業系統筆記
- 系統啟動, init
- Android系統啟動流程(四)Launcher啟動過程與系統啟動流程Android
- SpringBoot原始碼學習4——SpringBoot內嵌Tomcat啟動流程原始碼分析Spring Boot原始碼Tomcat
- oracle 隨系統開啟,自動啟動Oracle
- LiteOS-任務篇-原始碼分析-系統啟動函式原始碼函式
- Xilinx-ZYNQ7000系列-學習筆記(3):系統復位與啟動筆記
- 學習筆記:Linux的系統停止與重啟動命令詳解(轉)筆記Linux
- 如何系統學習C 語言(下)之 檔案篇
- 如何系統學習C 語言(上)之 基礎篇
- 如何[免]學習使用Linux系統?【伺服器篇】Linux伺服器
- 《SpringBoot篇:002》《SpringBoot的三種啟動方式:main、Maven、jar》Spring BootAIMavenJAR
- 系統的啟動流程
- 身為學霸的我學習linux系統之基礎篇Linux
- 移動地理資訊系統學習筆記筆記
- cms系統學習
- Android 系統開發_啟動階段篇 — 深入鑽研 SystemServerAndroidServer
- Android 系統開發_啟動階段篇 — 深入鑽研 initAndroid
- 【學習筆記之作業系統原理篇】儲存管理筆記作業系統
- 如何系統學習C 語言(中)之 結構體篇結構體
- 3.1.5.8 隨系統啟動自動啟動資料庫資料庫
- SpringBoot 啟動原理Spring Boot
- springboot啟動流程Spring Boot
- SpringBoot啟動原理Spring Boot
- 《10分鐘剖析》系統啟動2——啟動zygoteGo
- Android系統啟動自動開啟mtklogAndroid
- 前端系統學習——前端學習路線前端
- 用深度學習DIY自動化監控系統深度學習
- 系統學習iOS動畫之二:自動佈局iOS動畫
- nginx 框架學習(一)啟動流程Nginx框架
- 使用 flutter 啟動系統桌面Flutter
- mac系統如何啟動mysqlMacMySql
- Android 系統啟動流程Android