全網(wǎng)最詳細(xì)的介紹SpringBoot啟動(dòng)過(guò)程源碼分析

概述

上一篇我們介紹了SpringBoot的自動(dòng)裝配的知識(shí),這一篇我們將介紹SpringBoot最核心的知識(shí)點(diǎn),SpringBoot應(yīng)用的啟動(dòng)過(guò)程。這個(gè)啟動(dòng)過(guò)程比較復(fù)雜,在此我只介紹核心的知識(shí)點(diǎn)。其啟動(dòng)過(guò)程大概分為兩步。1. 初始化SpringApplication對(duì)象,2.執(zhí)行SpringApplication對(duì)象的run方法。
SpringBoot啟動(dòng)流程圖(以SpringBoot 1.5.8.RELEASE為例)

那我們就根據(jù)上面的啟動(dòng)流程圖進(jìn)行分析。
初始化SpingApplication對(duì)象

我們直接找到初始化SpingApplication對(duì)象的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的實(shí)現(xiàn)類(lèi)
	setInitializers((Collection) getSpringFactoriesInstances(
			ApplicationContextInitializer.class));
	//初始化ApplicationListener的實(shí)現(xiàn)類(lèi)
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

如上初始化SpringApplication對(duì)象,主要的步驟有兩步

加載spring.factories中ApplicationContextInitializer的配置類(lèi)
加載spring.factories中ApplicationListener的配置類(lèi)
都是通過(guò)SpringFactoriesLoader找到META-INF/spring.factories文件下配置了ApplicationContextInitializer和ApplicationListener兩個(gè)接口的實(shí)現(xiàn)類(lèi),并且進(jìn)行實(shí)例化。

其中ApplicationContextInitializer接口主要目的是ConfigurableApplicationContext做refresh之前,對(duì)ConfigurableApplicationContext實(shí)例做進(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則是一個(gè)監(jiān)聽(tīng)器,他是Spring框架對(duì)Java事件監(jiān)聽(tīng)機(jī)制的?種框架實(shí)現(xiàn)。
執(zhí)行Run方法

說(shuō)完了初始化SpingApplication對(duì)象的過(guò)程,接下來(lái)讓我們看看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個(gè)步驟。

  1. 加載SpringApplicationRunListeners

首先第一步是:通過(guò)SpringFactoriesLoader 到META-INF/spring.factories查找并加載所有的SpringApplicationRunListeners,通過(guò)start()方法通知所有的SpringApplicationRunListener,本質(zhì)上這是一個(gè)事件發(fā)布者,他在SpringBoot應(yīng)用啟動(dòng)的不同階段會(huì)發(fā)布不同的事件類(lèi)型。SpringApplicationRunListener接口只有一個(gè)實(shí)現(xiàn)類(lèi)EventPublishingRunListener,也就是說(shuō)SpringApplicationRunListeners類(lèi)的List listeners中只會(huì)生成一個(gè)EventPublishingRunListener實(shí)例。那么SpringApplicationRunListeners是如何發(fā)布事件類(lèi)型的呢?首先我們看下SpringApplicationRunListener這個(gè)接口。

public interface SpringApplicationRunListener {
/**
* run方法剛執(zhí)行時(shí)通知
/
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被加載好之后,但是沒(méi)有被刷新之前通知
/
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)用啟動(dòng)完成之后通知
*/
void finished(ConfigurableApplicationContext context, Throwable exception);
}

如上我們看到SpringApplicationRunListener監(jiān)聽(tīng)器SpringBoot應(yīng)用啟動(dòng)的不同階段都會(huì)有相應(yīng)的監(jiān)聽(tīng)通知。通知貫穿了SpringBoot應(yīng)用啟動(dòng)的完成過(guò)程。我們以environmentPrepared通知為例看看,SpringApplicationRunListener是如何發(fā)布事件類(lèi)型的,在其實(shí)現(xiàn)類(lèi)EventPublishingRunListener中有屬性名為initialMulticaster的SimpleApplicationEventMulticaster實(shí)例。在environmentPrepared方法中調(diào)用了SimpleApplicationEventMulticaster的multicastEvent方法,說(shuō)明發(fā)布過(guò)程被委托給了SimpleApplicationEventMulticaster類(lèi),其中在multicastEvent方法中指定了相應(yīng)的事件類(lèi)型。

public void environmentPrepared(ConfigurableEnvironment environment) {
	this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
			this.application, this.args, environment));
}
  1. 創(chuàng)建并配置當(dāng)前應(yīng)用將要使用的環(huán)境

    private ConfigurableEnvironment prepareEnvironment(
    SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments) {
    // 獲取創(chuàng)建的環(huán)境,如果沒(méi)有則創(chuàng)建,如果是web環(huán)境則創(chuàng)建StandardServletEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    //配置Environment:配置profile以及properties
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    //調(diào)?SpringApplicationRunListener的 environmentPrepared()?法,通知事件監(jiān)聽(tīng)者:應(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)前的運(yùn)行環(huán)境,其抽象了兩方面的內(nèi)容:1. 配置文件(profile)和屬性(properties),我們知道不同的環(huán)境(開(kāi)發(fā)環(huán)境,測(cè)試環(huán)境,發(fā)布環(huán)境)可以使用不同的屬性配置,這些屬性配置可以從配置文件,環(huán)境變量,命令行參數(shù)等來(lái)源獲取。因此,當(dāng)Environment準(zhǔn)備好之后,在整個(gè)應(yīng)用的任何時(shí)候,都可以獲取這些屬性。
所以,第二步的做的事情主要有如下三件:

獲取創(chuàng)建的環(huán)境(Environment),如果沒(méi)有則創(chuàng)建,如果是web環(huán)境則創(chuàng)建StandardServletEnvironment,如果不是的話則創(chuàng)建StandardEnvironment。
配置環(huán)境(Environment):主要是配置profile和屬性properties。
調(diào)用SpringApplicationRunListener的environmentPrepared方法,通知事件監(jiān)聽(tīng)者:應(yīng)用環(huán)境(Environment)已經(jīng)準(zhǔn)備好了。

3.設(shè)置SpringBoot應(yīng)用在啟動(dòng)時(shí)輸出的Banner。

第三步是設(shè)置SpringBoot應(yīng)用在啟動(dòng)時(shí)輸出的Banner,默認(rèn)的Banner如下圖所示:
在這里插入圖片描述

當(dāng)然我們也可以修改默認(rèn)的Banner,修改的方法就是在resources下新建一個(gè)banner.txt文件,替換掉默認(rèn)的banner。
4. 根據(jù)是否是web項(xiàng)目,來(lái)創(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)過(guò)了前面的初始化SpingApplication對(duì)象的過(guò)程,我們就已經(jīng)知道了當(dāng)前應(yīng)用的環(huán)境,那么如果是web應(yīng)用,則創(chuàng)建AnnotationConfigEmbeddedWebApplicationContext對(duì)象,否則創(chuàng)建AnnotationConfigApplicationContext對(duì)象。
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)建的流程依然是通過(guò)SpringFactoriesLoader獲取所有的FailureAnalyzer接口的實(shí)現(xiàn)類(lèi)名稱(chēng),然后創(chuàng)建對(duì)應(yīng)的實(shí)例。FailureAnalyzer的作用是用于分析故障并提供相關(guān)的診斷信息。
6. 初始化ApplicationContext

前面第四步,我們已經(jīng)創(chuàng)建好了與本應(yīng)用環(huán)境相匹配的ApplicationContext實(shí)例,那么第六步,就是對(duì)ApplicationContext進(jìn)行初始化了。這一步也是比較核心的一步。首先讓我們來(lái)看看實(shí)現(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() 方法來(lái)對(duì)已經(jīng)創(chuàng)建好的 ApplicationContext 進(jìn)行進(jìn)一步的處理。
applyInitializers(context);
//3. 調(diào)用SpringApplicationRunListeners的 contextPrepared() 方法,通知所有的監(jiān)聽(tīng)者,ApplicationContext已經(jīng)準(zhǔn)備完畢
listeners.contextPrepared(context);
//4. 將applicationArguments實(shí)例注入到IOC容器
context.getBeanFactory().registerSingleton(“springApplicationArguments”,
applicationArguments);
if (printedBanner != null) {
//5. 將printedBanner實(shí)例注入到IOC容器
context.getBeanFactory().registerSingleton(“springBootBanner”, printedBanner);
}
//6. 加載資源,這里的資源一般是啟動(dòng)類(lèi)xxxApplication
Set sources = getSources();
//7. 將所有的bean加載到容器中
load(context, sources.toArray(new Object[sources.size()]));
//8. 調(diào)?SpringApplicationRunListener的 contextLoaded()?法,通知所有的監(jiān)聽(tīng)者:ApplicationContext已經(jīng)裝載完畢
listeners.contextLoaded(context);
}

如上就是初始化ApplicationContext的主要邏輯,主要有如下邏輯:

將準(zhǔn)備好的Environment設(shè)置給ApplicationContext
遍歷調(diào)用所有的ApplicationContextInitializer的 initialize() 方法來(lái)對(duì)已經(jīng)創(chuàng)建好的 ApplicationContext 進(jìn)行進(jìn)一步的處理。
調(diào)用SpringApplicationRunListeners的 contextPrepared() 方法,通知所有的監(jiān)聽(tīng)者,ApplicationContext已經(jīng)準(zhǔn)備完畢
將applicationArguments實(shí)例注入到IOC容器。
將printedBanner實(shí)例注入到IOC容器,這個(gè)就是第三步生成的Banner的實(shí)例。
加載資源,這里的資源一般是啟動(dòng)類(lèi)xxxApplication
將所有的bean加載到容器中
調(diào)?SpringApplicationRunListeners的 contextLoaded()?法,通知所有的監(jiān)聽(tīng)者:ApplicationContext已經(jīng)裝載完畢。
  1. 調(diào)用ApplicationContext的refresh() 方法

第七步就是調(diào)用ApplicationContext的refresh() 方法,完成IOC容器的最后一道工序,為何要刷新容器呢?主要就是插手容器的啟動(dòng)。這里的 SpringApplication的 refresh方法最終還是調(diào)用到AbstractApplicationContext的refresh方法。
說(shuō)到AbstractApplicationContext的refresh方法,就要回到我們前面說(shuō)的Bean的生命周期。一個(gè)是BeanFactoryProcessor接口,用于插手容器的初始化。另外一個(gè)是BeanPostProcessor接口,用于插手Bean的實(shí)例化。
8.查找當(dāng)前context中是否注冊(cè)

查找當(dāng)前context中是否注冊(cè)有CommandLineRunner和ApplicationRunner,
如果有則遍歷執(zhí)行它們。
9.執(zhí)行所有SpringApplicationRunListener的finished() 方法
對(duì)run方法的斷點(diǎn)調(diào)試

1.5.8 版本的

在這里插入圖片描述

2.1.3 版本的

在這里插入圖片描述

總結(jié)

這就是Spring Boot的整個(gè)啟動(dòng)流程,其核?就是在Spring容器初始化并啟動(dòng)的基礎(chǔ)上加?各種擴(kuò)展點(diǎn),這些擴(kuò)展點(diǎn)包括:
ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等。你對(duì)整個(gè)流程的細(xì)節(jié)不必太過(guò)關(guān)注,你只要理解這些擴(kuò)展點(diǎn)是在何時(shí)如何?作的,能讓它們?yōu)槟闼?即可。





作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥