概述
上一篇我們介紹了SpringBoot的自動裝配的知識,這一篇我們將介紹SpringBoot最核心的知識點,SpringBoot應用的啓動過程。這個啓動過程比較複雜,在此我只介紹核心的知識點。其啓動過程大概分爲兩步。1. 初始化SpringApplication對象,2.執行SpringApplication對象的run方法。
SpringBoot啓動流程圖(以SpringBoot 1.5.8.RELEASE爲例)
那我們就根據上面的啓動流程圖進行分析。
初始化SpingApplication對象
我們直接找到初始化SpingApplication
對象的initialize
方法。
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//檢查當前環境是否是web環境
this.webEnvironment = deduceWebEnvironment();
//初始化ApplicationContextInitializer的實現類
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//初始化ApplicationListener的實現類
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
如上初始化SpringApplication
對象,主要的步驟有兩步
- 初始化
ApplicationContextInitializer
的實現類 - 初始化
ApplicationListener
的實現類
都是通過SpringFactoriesLoader
找到META-INF/spring.factories
文件下配置了ApplicationContextInitializer
和ApplicationListener
兩個接口的實現類,並且進行實例化。其中ApplicationContextInitializer
接口主要目的是ConfigurableApplicationContext
做refresh之前,對ConfigurableApplicationContext
實例做進一步的設置或處理。
如下圖所示:
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
而ApplicationListener
則是一個監聽器,他是Spring框架對Java事件監聽機制的⼀種框架實現。
執行Run方法
說完了初始化SpingApplication
對象的過程,接下來讓我們看看run()
方法的執行邏輯。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
//1
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//2
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//3
Banner printedBanner = printBanner(environment);
//4
context = createApplicationContext();
//5
analyzers = new FailureAnalyzers(context);
//6
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//7
refreshContext(context);
//8
afterRefresh(context, applicationArguments);
//9
listeners.finished(context, null);
stopWatch.stop();
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
如上,就是執行run方法的主要邏輯,主要分爲9個步驟。
1. 加載SpringApplicationRunListeners
首先第一步是:通過SpringFactoriesLoader 到META-INF/spring.factories
查找並加載所有的SpringApplicationRunListeners
,通過start()
方法通知所有的SpringApplicationRunListener
,本質上這是一個事件發佈者,他在SpringBoot應用啓動的不同階段會發布不同的事件類型。SpringApplicationRunListener
接口只有一個實現類EventPublishingRunListener
,也就是說SpringApplicationRunListeners
類的List<SpringApplicationRunListener> listeners
中只會生成一個EventPublishingRunListener
實例。那麼SpringApplicationRunListeners
是如何發佈事件類型的呢?首先我們看下SpringApplicationRunListener
這個接口。
public interface SpringApplicationRunListener {
/**
* run方法剛執行時通知
*/
void starting();
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
Environment準備好,ApplicationContext被創建好之前通知
*/
void environmentPrepared(ConfigurableEnvironment environment);
/**
* Called once the {@link ApplicationContext} has been created and prepared, but
* before sources have been loaded.
ApplicationContext被創建好之後,但是資源加載好之前通知
*/
void contextPrepared(ConfigurableApplicationContext context);
/**
* Called once the application context has been loaded but before it has been
* refreshed.
ApplicationContext被加載好之後,但是沒有被刷新之前通知
*/
void contextLoaded(ConfigurableApplicationContext context);
/**
* Called immediately before the run method finishes.
* @param context the application context or null if a failure occurred before the
* context was created
應用啓動完成之後通知
*/
void finished(ConfigurableApplicationContext context, Throwable exception);
}
如上我們看到SpringApplicationRunListener
監聽器SpringBoot應用啓動的不同階段都會有相應的監聽通知。通知貫穿了SpringBoot應用啓動的完成過程。我們以environmentPrepared
通知爲例看看,SpringApplicationRunListener
是如何發佈事件類型的,在其實現類EventPublishingRunListener
中有屬性名爲initialMulticaster
的SimpleApplicationEventMulticaster
實例。在environmentPrepared
方法中調用了SimpleApplicationEventMulticaster
的multicastEvent
方法,說明發布過程被委託給了SimpleApplicationEventMulticaster
類,其中在multicastEvent
方法中指定了相應的事件類型。
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
2. 創建並配置當前應用將要使用的環境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 獲取創建的環境,如果沒有則創建,如果是web環境則創建StandardServletEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置Environment:配置profile以及properties
configureEnvironment(environment, applicationArguments.getSourceArgs());
//調⽤SpringApplicationRunListener的 environmentPrepared()⽅法,通知事件監聽者:應⽤的Environment已經準備好
listeners.environmentPrepared(environment);
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
第二步是創建並配置當前應用的環境(Environment),Environment用於描述應用程序當前的運行環境,其抽象了兩方面的內容:1. 配置文件(profile)和屬性(properties),我們知道不同的環境(開發環境,測試環境,發佈環境)可以使用不同的屬性配置,這些屬性配置可以從配置文件,環境變量,命令行參數等來源獲取。因此,當Environment準備好之後,在整個應用的任何時候,都可以獲取這些屬性。
所以,第二步的做的事情主要有如下三件:
- 獲取創建的環境(Environment),如果沒有則創建,如果是web環境則創建
StandardServletEnvironment
,如果不是的話則創建StandardEnvironment
。 - 配置環境(Environment):主要是配置profile和屬性properties。
- 調用
SpringApplicationRunListener
的environmentPrepared
方法,通知事件監聽者:應用環境(Environment)已經準備好了。
3.設置SpringBoot應用在啓動時輸出的Banner。
第三步是設置SpringBoot應用在啓動時輸出的Banner,默認的Banner如下圖所示:
當然我們也可以修改默認的Banner,修改的方法就是在resources下新建一個banner.txt文件,替換掉默認的banner。
4. 根據是否是web項目,來創建不同的ApplicationContext容器
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
第四步是:創建不同的ApplicationContext容器;經過了前面的初始化SpingApplication對象的過程,我們就已經知道了當前應用的環境,那麼如果是web應用,則創建AnnotationConfigEmbeddedWebApplicationContext
對象,否則創建AnnotationConfigApplicationContext
對象。
5. 創建一系列的FailureAnalyzer
FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
this.classLoader = (classLoader == null ? context.getClassLoader() : classLoader);
this.analyzers = loadFailureAnalyzers(this.classLoader);
prepareFailureAnalyzers(this.analyzers, context);
}
第五步是創建FailureAnalyzer
的代碼如上所示:創建的流程依然是通過SpringFactoriesLoader
獲取所有的FailureAnalyzer
接口的實現類名稱,然後創建對應的實例。FailureAnalyzer
的作用是用於分析故障並提供相關的診斷信息。
6. 初始化ApplicationContext
前面第四步,我們已經創建好了與本應用環境相匹配的ApplicationContext實例,那麼第六步,就是對ApplicationContext
進行初始化了。這一步也是比較核心的一步。首先讓我們來看看實現邏輯的相關代碼:
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//1. 將準備好的Environment設置給ApplicationContext
context.setEnvironment(environment);
postProcessApplicationContext(context);
//2. 遍歷調用所有的ApplicationContextInitializer的 initialize() 方法來對已經創建好的 ApplicationContext 進行進一步的處理。
applyInitializers(context);
//3. 調用SpringApplicationRunListeners的 contextPrepared() 方法,通知所有的監聽者,ApplicationContext已經準備完畢
listeners.contextPrepared(context);
//4. 將applicationArguments實例注入到IOC容器
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
//5. 將printedBanner實例注入到IOC容器
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
//6. 加載資源,這裏的資源一般是啓動類xxxApplication
Set<Object> sources = getSources();
//7. 將所有的bean加載到容器中
load(context, sources.toArray(new Object[sources.size()]));
//8. 調⽤SpringApplicationRunListener的 contextLoaded()⽅法,通知所有的監聽者:ApplicationContext已經裝載完畢
listeners.contextLoaded(context);
}
如上就是初始化ApplicationContext
的主要邏輯,主要有如下邏輯:
- 將準備好的
Environment
設置給ApplicationContext
- 遍歷調用所有的
ApplicationContextInitializer
的initialize()
方法來對已經創建好的ApplicationContext
進行進一步的處理。 - 調用
SpringApplicationRunListeners
的contextPrepared()
方法,通知所有的監聽者,ApplicationContext
已經準備完畢 - 將
applicationArguments
實例注入到IOC容器。 - 將
printedBanner
實例注入到IOC容器,這個就是第三步生成的Banner的實例。 - 加載資源,這裏的資源一般是啓動類xxxApplication
- 將所有的bean加載到容器中
- 調⽤
SpringApplicationRunListeners
的contextLoaded()
⽅法,通知所有的監聽者:ApplicationContext
已經裝載完畢。
7. 調用ApplicationContext的refresh() 方法
第七步就是調用ApplicationContext的refresh() 方法,完成IOC容器的最後一道工序,爲何要刷新容器呢?主要就是插手容器的啓動。這裏的 SpringApplication
的 refresh
方法最終還是調用到AbstractApplicationContext
的refresh
方法。
說到AbstractApplicationContext
的refresh
方法,就要回到我們前面說的Bean的生命週期。一個是BeanFactoryProcessor
接口,用於插手容器的初始化。另外一個是BeanPostProcessor
接口,用於插手Bean的實例化。
8.查找當前context中是否註冊
查找當前context中是否註冊有CommandLineRunner和ApplicationRunner,
如果有則遍歷執行它們。
9.執行所有SpringApplicationRunListener的finished() 方法
對run方法的斷點調試
- 1.5.8 版本的
- 2.1.3 版本的
總結
這就是Spring Boot的整個啓動流程,其核⼼就是在Spring容器初始化並啓動的基礎上加⼊各種擴展點,這些擴展點包括:
ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等。你對整個流程的細節不必太過關注,你只要理解這些擴展點是在何時如何⼯作的,能讓它們爲你所⽤即可。