全網(wǎng)最詳細(xì)的介紹SpringBoot啟動過程源碼分析
概述
上一篇我們介紹了SpringBoot的自動裝配的知識,這一篇我們將介紹SpringBoot最核心的知識點,SpringBoot應(yīng)用的啟動過程。這個啟動過程比較復(fù)雜,在此我只介紹核心的知識點。其啟動過程大概分為兩步。1. 初始化SpringApplication對象,2.執(zhí)行SpringApplication對象的run方法。
SpringBoot啟動流程圖(以SpringBoot 1.5.8.RELEASE為例)
那我們就根據(jù)上面的啟動流程圖進(jìn)行分析。
初始化SpingApplication對象
我們直接找到初始化SpingApplication
對象的initialize
方法。
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//檢查當(dāng)前環(huán)境是否是web環(huán)境
this.webEnvironment = deduceWebEnvironment();
//初始化ApplicationContextInitializer的實現(xiàn)類
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//初始化ApplicationListener的實現(xiàn)類
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
如上初始化SpringApplication
對象,主要的步驟有兩步
- 加載spring.factories中ApplicationContextInitializer的配置類
- 加載spring.factories中ApplicationListener的配置類
都是通過SpringFactoriesLoader
找到META-INF/spring.factories
文件下配置了ApplicationContextInitializer
和ApplicationListener
兩個接口的實現(xiàn)類,并且進(jìn)行實例化。
其中ApplicationContextInitializer
接口主要目的是ConfigurableApplicationContext
做refresh之前,對ConfigurableApplicationContext
實例做進(jìn)一步的設(shè)置或處理。如下圖所示:
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
則是一個監(jiān)聽器,他是Spring框架對Java事件監(jiān)聽機(jī)制的?種框架實現(xiàn)。
執(zhí)行Run方法
說完了初始化SpingApplication
對象的過程,接下來讓我們看看run()
方法的執(zhí)行邏輯。
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);
}
如上,就是執(zhí)行run方法的主要邏輯,主要分為9個步驟。
1. 加載SpringApplicationRunListeners
首先第一步是:通過SpringFactoriesLoader 到META-INF/spring.factories
查找并加載所有的SpringApplicationRunListeners
,通過start()
方法通知所有的SpringApplicationRunListener
,本質(zhì)上這是一個事件發(fā)布者,他在SpringBoot應(yīng)用啟動的不同階段會發(fā)布不同的事件類型。SpringApplicationRunListener
接口只有一個實現(xiàn)類EventPublishingRunListener
,也就是說SpringApplicationRunListeners
類的List<SpringApplicationRunListener> listeners
中只會生成一個EventPublishingRunListener
實例。那么SpringApplicationRunListeners
是如何發(fā)布事件類型的呢?首先我們看下SpringApplicationRunListener
這個接口。
public interface SpringApplicationRunListener {
/**
* run方法剛執(zhí)行時通知
*/
void starting();
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
Environment準(zhǔn)備好,ApplicationContext被創(chuàng)建好之前通知
*/
void environmentPrepared(ConfigurableEnvironment environment);
/**
* Called once the {@link ApplicationContext} has been created and prepared, but
* before sources have been loaded.
ApplicationContext被創(chuàng)建好之后,但是資源加載好之前通知
*/
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
應(yīng)用啟動完成之后通知
*/
void finished(ConfigurableApplicationContext context, Throwable exception);
}
如上我們看到SpringApplicationRunListener
監(jiān)聽器SpringBoot應(yīng)用啟動的不同階段都會有相應(yīng)的監(jiān)聽通知。通知貫穿了SpringBoot應(yīng)用啟動的完成過程。我們以environmentPrepared
通知為例看看,SpringApplicationRunListener
是如何發(fā)布事件類型的,在其實現(xiàn)類EventPublishingRunListener
中有屬性名為initialMulticaster
的SimpleApplicationEventMulticaster
實例。在environmentPrepared
方法中調(diào)用了SimpleApplicationEventMulticaster
的multicastEvent
方法,說明發(fā)布過程被委托給了SimpleApplicationEventMulticaster
類,其中在multicastEvent
方法中指定了相應(yīng)的事件類型。
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
2. 創(chuàng)建并配置當(dāng)前應(yīng)用將要使用的環(huán)境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 獲取創(chuàng)建的環(huán)境,如果沒有則創(chuàng)建,如果是web環(huán)境則創(chuàng)建StandardServletEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置Environment:配置profile以及properties
configureEnvironment(environment, applicationArguments.getSourceArgs());
//調(diào)?SpringApplicationRunListener的 environmentPrepared()?法,通知事件監(jiān)聽者:應(yīng)?的Environment已經(jīng)準(zhǔn)備好
listeners.environmentPrepared(environment);
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
第二步是創(chuàng)建并配置當(dāng)前應(yīng)用的環(huán)境(Environment),Environment用于描述應(yīng)用程序當(dāng)前的運行環(huán)境,其抽象了兩方面的內(nèi)容:1. 配置文件(profile)和屬性(properties),我們知道不同的環(huán)境(開發(fā)環(huán)境,測試環(huán)境,發(fā)布環(huán)境)可以使用不同的屬性配置,這些屬性配置可以從配置文件,環(huán)境變量,命令行參數(shù)等來源獲取。因此,當(dāng)Environment準(zhǔn)備好之后,在整個應(yīng)用的任何時候,都可以獲取這些屬性。
所以,第二步的做的事情主要有如下三件:
- 獲取創(chuàng)建的環(huán)境(Environment),如果沒有則創(chuàng)建,如果是web環(huán)境則創(chuàng)建
StandardServletEnvironment
,如果不是的話則創(chuàng)建StandardEnvironment
。 - 配置環(huán)境(Environment):主要是配置profile和屬性properties。
- 調(diào)用
SpringApplicationRunListener
的environmentPrepared
方法,通知事件監(jiān)聽者:應(yīng)用環(huán)境(Environment)已經(jīng)準(zhǔn)備好了。
3.設(shè)置SpringBoot應(yīng)用在啟動時輸出的Banner。
第三步是設(shè)置SpringBoot應(yīng)用在啟動時輸出的Banner,默認(rèn)的Banner如下圖所示:
當(dāng)然我們也可以修改默認(rèn)的Banner,修改的方法就是在resources下新建一個banner.txt文件,替換掉默認(rèn)的banner。
4. 根據(jù)是否是web項目,來創(chuàng)建不同的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);
}
第四步是:創(chuàng)建不同的ApplicationContext容器;經(jīng)過了前面的初始化SpingApplication對象的過程,我們就已經(jīng)知道了當(dāng)前應(yīng)用的環(huán)境,那么如果是web應(yīng)用,則創(chuàng)建AnnotationConfigEmbeddedWebApplicationContext
對象,否則創(chuàng)建AnnotationConfigApplicationContext
對象。
5. 創(chuàng)建一系列的FailureAnalyzer
FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
this.classLoader = (classLoader == null ? context.getClassLoader() : classLoader);
this.analyzers = loadFailureAnalyzers(this.classLoader);
prepareFailureAnalyzers(this.analyzers, context);
}
第五步是創(chuàng)建FailureAnalyzer
的代碼如上所示:創(chuàng)建的流程依然是通過SpringFactoriesLoader
獲取所有的FailureAnalyzer
接口的實現(xiàn)類名稱,然后創(chuàng)建對應(yīng)的實例。FailureAnalyzer
的作用是用于分析故障并提供相關(guān)的診斷信息。
6. 初始化ApplicationContext
前面第四步,我們已經(jīng)創(chuàng)建好了與本應(yīng)用環(huán)境相匹配的ApplicationContext實例,那么第六步,就是對ApplicationContext
進(jìn)行初始化了。這一步也是比較核心的一步。首先讓我們來看看實現(xiàn)邏輯的相關(guān)代碼:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //1. 將準(zhǔn)備好的Environment設(shè)置給ApplicationContext context.setEnvironment(environment); postProcessApplicationContext(context); //2. 遍歷調(diào)用所有的ApplicationContextInitializer的 initialize() 方法來對已經(jīng)創(chuàng)建好的 ApplicationContext 進(jìn)行進(jìn)一步的處理。 applyInitializers(context); //3. 調(diào)用SpringApplicationRunListeners的 contextPrepared() 方法,通知所有的監(jiān)聽者,ApplicationContext已經(jīng)準(zhǔn)備完畢 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. 調(diào)?SpringApplicationRunListener的 contextLoaded()?法,通知所有的監(jiān)聽者:ApplicationContext已經(jīng)裝載完畢 listeners.contextLoaded(context); }
如上就是初始化ApplicationContext
的主要邏輯,主要有如下邏輯:
- 將準(zhǔn)備好的
Environment
設(shè)置給ApplicationContext
- 遍歷調(diào)用所有的
ApplicationContextInitializer
的initialize()
方法來對已經(jīng)創(chuàng)建好的ApplicationContext
進(jìn)行進(jìn)一步的處理。 - 調(diào)用
SpringApplicationRunListeners
的contextPrepared()
方法,通知所有的監(jiān)聽者,ApplicationContext
已經(jīng)準(zhǔn)備完畢 - 將
applicationArguments
實例注入到IOC容器。 - 將
printedBanner
實例注入到IOC容器,這個就是第三步生成的Banner的實例。 - 加載資源,這里的資源一般是啟動類xxxApplication
- 將所有的bean加載到容器中
- 調(diào)?
SpringApplicationRunListeners
的contextLoaded()
?法,通知所有的監(jiān)聽者:ApplicationContext
已經(jīng)裝載完畢。
7. 調(diào)用ApplicationContext的refresh() 方法
第七步就是調(diào)用ApplicationContext的refresh() 方法,完成IOC容器的最后一道工序,為何要刷新容器呢?主要就是插手容器的啟動。這里的 SpringApplication
的 refresh
方法最終還是調(diào)用到AbstractApplicationContext
的refresh
方法。
說到AbstractApplicationContext
的refresh
方法,就要回到我們前面說的Bean的生命周期。一個是BeanFactoryProcessor
接口,用于插手容器的初始化。另外一個是BeanPostProcessor
接口,用于插手Bean的實例化。
8.查找當(dāng)前context中是否注冊
查找當(dāng)前context中是否注冊有CommandLineRunner和ApplicationRunner,
如果有則遍歷執(zhí)行它們。
9.執(zhí)行所有SpringApplicationRunListener的finished() 方法
對run方法的斷點調(diào)試
- 1.5.8 版本的
- 2.1.3 版本的
總結(jié)
這就是Spring Boot的整個啟動流程,其核?就是在Spring容器初始化并啟動的基礎(chǔ)上加?各種擴(kuò)展點,這些擴(kuò)展點包括:
ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等。你對整個流程的細(xì)節(jié)不必太過關(guān)注,你只要理解這些擴(kuò)展點是在何時如何?作的,能讓它們?yōu)槟闼?即可。
作者:碼農(nóng)飛哥
微信公眾號:碼農(nóng)飛哥