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

概述

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

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

我們直接找到初始化SpingApplication對象的initialize方法。

private void initialize(Object[] sources) {
	if (sources != null && sources.length > 0) {
		this.sources.addAll(Arrays.asList(sources));
	}
	//檢查當前環(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)類,并且進行實例化。

其中ApplicationContextInitializer接口主要目的是ConfigurableApplicationContext做refresh之前,對ConfigurableApplicationContext實例做進一步的設(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)聽機制的?種框架實現(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 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準備好,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));
}
  1. 創(chuà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)準備好
    listeners.environmentPrepared(environment);
    if (!this.webEnvironment) {
    environment = new EnvironmentConverter(getClassLoader())
    .convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
    }

第二步是創(chuàng)建并配置當前應(yīng)用的環(huán)境(Environment),Environment用于描述應(yīng)用程序當前的運行環(huán)境,其抽象了兩方面的內(nèi)容:1. 配置文件(profile)和屬性(properties),我們知道不同的環(huán)境(開發(fā)環(huán)境,測試環(huán)境,發(fā)布環(huán)境)可以使用不同的屬性配置,這些屬性配置可以從配置文件,環(huán)境變量,命令行參數(shù)等來源獲取。因此,當Environment準備好之后,在整個應(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)準備好了。

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

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

當然我們也可以修改默認的Banner,修改的方法就是在resources下新建一個banner.txt文件,替換掉默認的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)知道了當前應(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進行初始化了。這一步也是比較核心的一步。首先讓我們來看看實現(xiàn)邏輯的相關(guān)代碼:

private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//1. 將準備好的Environment設(shè)置給ApplicationContext
context.setEnvironment(environment);
postProcessApplicationContext(context);
//2. 遍歷調(diào)用所有的ApplicationContextInitializer的 initialize() 方法來對已經(jīng)創(chuàng)建好的 ApplicationContext 進行進一步的處理。
applyInitializers(context);
//3. 調(diào)用SpringApplicationRunListeners的 contextPrepared() 方法,通知所有的監(jiān)聽者,ApplicationContext已經(jīng)準備完畢
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 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的主要邏輯,主要有如下邏輯:

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

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

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

1.5.8 版本的

在這里插入圖片描述

2.1.3 版本的

在這里插入圖片描述

總結(jié)

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





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