全網(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對象,主要的步驟有兩步

  1. 加載spring.factories中ApplicationContextInitializer的配置類
  2. 加載spring.factories中ApplicationListener的配置類
    都是通過SpringFactoriesLoader找到META-INF/spring.factories文件下配置了ApplicationContextInitializerApplicationListener兩個接口的實現(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中有屬性名為initialMulticasterSimpleApplicationEventMulticaster實例。在environmentPrepared方法中調(diào)用了SimpleApplicationEventMulticastermulticastEvent方法,說明發(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)用的任何時候,都可以獲取這些屬性。
所以,第二步的做的事情主要有如下三件:

  1. 獲取創(chuàng)建的環(huán)境(Environment),如果沒有則創(chuàng)建,如果是web環(huán)境則創(chuàng)建StandardServletEnvironment,如果不是的話則創(chuàng)建StandardEnvironment
  2. 配置環(huán)境(Environment):主要是配置profile和屬性properties。
  3. 調(diào)用SpringApplicationRunListenerenvironmentPrepared方法,通知事件監(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的主要邏輯,主要有如下邏輯:

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

7. 調(diào)用ApplicationContext的refresh() 方法

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

8.查找當(dāng)前context中是否注冊

查找當(dāng)前context中是否注冊有CommandLineRunner和ApplicationRunner,
如果有則遍歷執(zhí)行它們。

9.執(zhí)行所有SpringApplicationRunListener的finished() 方法

對run方法的斷點調(diào)試

  1. 1.5.8 版本的
    在這里插入圖片描述
  2. 2.1.3 版本的
    在這里插入圖片描述

總結(jié)

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




作者:碼農(nóng)飛哥

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