全網最詳細的介紹SpringBoot啓動過程源碼分析

概述

上一篇我們介紹了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對象,主要的步驟有兩步

  1. 初始化ApplicationContextInitializer的實現類
  2. 初始化ApplicationListener的實現類
    都是通過SpringFactoriesLoader找到META-INF/spring.factories文件下配置了ApplicationContextInitializerApplicationListener兩個接口的實現類,並且進行實例化。其中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中有屬性名爲initialMulticasterSimpleApplicationEventMulticaster實例。在environmentPrepared方法中調用了SimpleApplicationEventMulticastermulticastEvent方法,說明發布過程被委託給了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準備好之後,在整個應用的任何時候,都可以獲取這些屬性。
所以,第二步的做的事情主要有如下三件:

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

  1. 將準備好的Environment設置給ApplicationContext
  2. 遍歷調用所有的ApplicationContextInitializerinitialize() 方法來對已經創建好的 ApplicationContext 進行進一步的處理。
  3. 調用SpringApplicationRunListenerscontextPrepared() 方法,通知所有的監聽者,ApplicationContext已經準備完畢
  4. applicationArguments實例注入到IOC容器。
  5. printedBanner實例注入到IOC容器,這個就是第三步生成的Banner的實例。
  6. 加載資源,這裏的資源一般是啓動類xxxApplication
  7. 將所有的bean加載到容器中
  8. 調⽤SpringApplicationRunListenerscontextLoaded()⽅法,通知所有的監聽者:ApplicationContext已經裝載完畢。

7. 調用ApplicationContext的refresh() 方法

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

8.查找當前context中是否註冊

查找當前context中是否註冊有CommandLineRunner和ApplicationRunner,
如果有則遍歷執行它們。

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

對run方法的斷點調試

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

總結

這就是Spring Boot的整個啓動流程,其核⼼就是在Spring容器初始化並啓動的基礎上加⼊各種擴展點,這些擴展點包括:
ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等。你對整個流程的細節不必太過關注,你只要理解這些擴展點是在何時如何⼯作的,能讓它們爲你所⽤即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章