SpringBoot2 | SpringBoot啟動流程原始碼分析(一)

張書康發表於2019-04-02

微信公眾號:吉姆餐廳ak 學習更多原始碼知識,歡迎關注。

在這裡插入圖片描述


SpringBoot2 | SpringBoot啟動流程原始碼分析(一)

SpringBoot2 | SpringBoot啟動流程原始碼分析(二)

SpringBoot2 | @SpringBootApplication註解 自動化配置流程原始碼分析(三)

SpringBoot2 | SpringBoot Environment原始碼分析(四)

SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)

SpringBoot2 | SpringBoot監聽器原始碼分析 | 自定義ApplicationListener(六)

SpringBoot2 | 條件註解@ConditionalOnBean原理原始碼深度解析(七)

SpringBoot2 | Spring AOP 原理原始碼深度剖析(八)

SpringBoot2 | SpingBoot FilterRegistrationBean 註冊元件 | FilterChain 責任鏈原始碼分析(九)

SpringBoot2 | BeanDefinition 註冊核心類 ImportBeanDefinitionRegistrar (十)

SpringBoot2 | Spring 核心擴充套件介面 | 核心擴充套件方法總結(十一)


概述:

前陣子看到了SpringCloud社群的一個開源專案,主要是對服務發現增強的功能。研究專案的時候發現程式碼簡練,優雅,最主要是spring ioc和aop特性應用的得心應手。若非對原始碼有深入研究,不可能寫出這麼優秀的開源專案。另外在現有的springboot專欄中,大多數博文旨在應用,對一些中介軟體的整合之類,原始碼分析的部落格數量有限。鑑於以上兩方面,該系列應運而生。

該系列主要還是Spring的核心原始碼,不過目前Springboot大行其道,所以就從Springboot開始分析。最新版本是Springboot2.0.4,Spring5,所以新特性本系列後面也會著重分析。

整個系列會圍繞springboot啟動流程進行原始碼分析,在整個流程中,會遇到一些核心類或者核心流程,會著重講解,所以篇幅可能會增多,做好準備。


原始碼分析

首先是專案啟動類:

	public static void main(String[] args) {
		SpringApplication.run(MarsApplication.class, args);
	}
複製程式碼
	public SpringApplication(Object... sources) {
		//初始化
		initialize(sources);
	}
複製程式碼

初始化時,會載入META-INF/spring.factories檔案:

	private void initialize(Object[] sources) {
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
		//設定servlet環境
		this.webEnvironment = deduceWebEnvironment();
		//獲取ApplicationContextInitializer,也是在這裡開始首次載入spring.factories檔案
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		//獲取監聽器,這裡是第二次載入spring.factories檔案
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
複製程式碼

來看一下deduceWebEnvironment()方法:

	private WebApplicationType deduceWebApplicationType() {
		if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
				&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}
複製程式碼

這裡主要是通過判斷REACTIVE相關的位元組碼是否存在,如果不存在,則web環境即為SERVLET型別。這裡設定好web環境型別,在後面會根據型別初始化對應環境。

ApplicationContextInitializer是spring元件spring-context元件中的一個介面,主要是spring ioc容器重新整理之前的一個回撥介面,用於處於自定義邏輯。 spring.factories檔案中的實現類:

這裡寫圖片描述

這裡監聽器為9個:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
複製程式碼

還有1個為:org.springframework.boot.autoconfigure.BackgroundPreinitializer 這10個監聽器會貫穿springBoot整個生命週期。稍後會介紹。


這裡先繼續後面的流程。來看一下run方法:

public ConfigurableApplicationContext run(String... args) {
		//時間監控
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		//java.awt.headless是J2SE的一種模式用於在缺少螢幕、鍵盤或者滑鼠時的系統配置,很多監控工具如jconsole 需要將該值設定為true,系統變數預設為true
		configureHeadlessProperty();
		//獲取spring.factories中的監聽器變數,args為指定的引數陣列,預設為當前類SpringApplication
		//第一步:獲取並啟動監聽器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			//第二步:構造容器環境
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			//設定需要忽略的bean
			configureIgnoreBeanInfo(environment);
			//列印banner
			Banner printedBanner = printBanner(environment);
			//第三步:建立容器
			context = createApplicationContext();
			//第四步:例項化SpringBootExceptionReporter.class,用來支援報告關於啟動的錯誤
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//第五步:準備容器
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			//第六步:重新整理容器
			refreshContext(context);
			//第七步:重新整理容器後的擴充套件介面
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
複製程式碼
  • 第一步:獲取並啟動監聽器
  • 第二步:構造容器環境
  • 第三步:建立容器
  • 第四步:例項化SpringBootExceptionReporter.class,用來支援報告關於啟動的錯誤
  • 第五步:準備容器
  • 第六步:重新整理容器
  • 第七步:重新整理容器後的擴充套件介面

下面具體分析。


一:獲取並啟動監聽器

1)獲取監聽器

SpringApplicationRunListeners listeners = getRunListeners(args); 跟進getRunListeners方法:

	private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}
複製程式碼

上面可以看到,args本身預設為空,但是在獲取監聽器的方法中,getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)將當前物件作為引數,該方法用來獲取spring.factories對應的監聽器:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
複製程式碼

整個 springBoot 框架中獲取factories的方式統一如下:

	@SuppressWarnings("unchecked")
	private <T> List<T> createSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
			Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				//裝載class檔案到記憶體
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass
						.getDeclaredConstructor(parameterTypes);
				//主要通過反射建立例項
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException(
						"Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}
複製程式碼

上面通過反射獲取例項時會觸發EventPublishingRunListener的建構函式:

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}
複製程式碼

重點來看一下addApplicationListener方法:

	@Override
	public void addApplicationListener(ApplicationListener<?> listener) {
		synchronized (this.retrievalMutex) {
			// Explicitly remove target for a proxy, if registered already,
			// in order to avoid double invocations of the same listener.
			Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
			if (singletonTarget instanceof ApplicationListener) {
				this.defaultRetriever.applicationListeners.remove(singletonTarget);
			}
			//內部類物件
			this.defaultRetriever.applicationListeners.add(listener);
			this.retrieverCache.clear();
		}
	}
複製程式碼

上述方法定義在SimpleApplicationEventMulticaster父類AbstractApplicationEventMulticaster中。關鍵程式碼為this.defaultRetriever.applicationListeners.add(listener);,這是一個內部類,用來儲存所有的監聽器。也就是在這一步,將spring.factories中的監聽器傳遞到SimpleApplicationEventMulticaster中。 繼承關係如下:

這裡寫圖片描述

2)啟動監聽器:

listeners.starting();,獲取的監聽器為EventPublishingRunListener,從名字可以看出是啟動事件釋出監聽器,主要用來發布啟動事件。

	@Override
	public void starting() {
	//關鍵程式碼,這裡是建立application啟動事件`ApplicationStartingEvent`
		this.initialMulticaster.multicastEvent(
				new ApplicationStartingEvent(this.application, this.args));
	}
複製程式碼

EventPublishingRunListener這個是springBoot框架中最早執行的監聽器,在該監聽器執行started()方法時,會繼續釋出事件,也就是事件傳遞。這種實現主要還是基於spring的事件機制。 繼續跟進SimpleApplicationEventMulticaster,有個核心方法:

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			//獲取執行緒池,如果為空則同步處理。這裡執行緒池為空,還未沒初始化。
			Executor executor = getTaskExecutor();
			if (executor != null) {
			    //非同步傳送事件
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				//同步傳送事件
				invokeListener(listener, event);
			}
		}
	}
複製程式碼

這裡會根據事件型別ApplicationStartingEvent獲取對應的監聽器,在容器啟動之後執行響應的動作,有如下4種監聽器:

這裡寫圖片描述
這是springBoot啟動過程中,第一處根據型別,執行監聽器的地方。根據釋出的事件型別從上述10種監聽器中選擇對應的監聽器進行事件釋出,當然如果繼承了 springCloud或者別的框架,就不止10個了。這裡選了一個 springBoot 的日誌監聽器來進行講解,核心程式碼如下:

 @Override
    public void onApplicationEvent(ApplicationEvent event) {
        //在springboot啟動的時候
        if (event instanceof ApplicationStartedEvent) {
            onApplicationStartedEvent((ApplicationStartedEvent) event);
        }
        //springboot的Environment環境準備完成的時候
        else if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        //在springboot容器的環境設定完成以後
        else if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent((ApplicationPreparedEvent) event);
        }
        //容器關閉的時候
        else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
                .getApplicationContext().getParent() == null) {
            onContextClosedEvent();
        }
        //容器啟動失敗的時候
        else if (event instanceof ApplicationFailedEvent) {
            onApplicationFailedEvent();
        }
    }
複製程式碼

因為我們的事件型別為ApplicationEvent,所以會執行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot會在執行過程中的不同階段,傳送各種事件,來執行對應監聽器的對應方法。大同小異,別的監聽器執行流程這裡不再贅述,後面會有單獨的詳解。 繼續後面的流程。


二:環境構建:

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); 跟進去該方法:

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		//獲取對應的ConfigurableEnvironment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//配置
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//釋出環境已準備事件,這是第二次釋出事件
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
複製程式碼

來看一下getOrCreateEnvironment()方法,前面已經提到,environment已經被設定了servlet型別,所以這裡建立的是環境物件是StandardServletEnvironment

	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		if (this.webApplicationType == WebApplicationType.SERVLET) {
			return new StandardServletEnvironment();
		}
		return new StandardEnvironment();
	}
複製程式碼

列舉類WebApplicationType是springBoot2新增的特性,主要針對spring5引入的reactive特性。列舉型別如下:

public enum WebApplicationType {
	//不需要再web容器的環境下執行,普通專案
	NONE,
	//基於servlet的web專案
	SERVLET,
	//這個是spring5版本開始的新特性
	REACTIVE
}
複製程式碼

Environment介面提供了4種實現方式,StandardEnvironmentStandardServletEnvironmentMockEnvironmentStandardReactiveWebEnvironment,分別代表普通程式、Web程式、測試程式的環境、響應式web環境,具體後面會詳細講解。 這裡只需要知道在返回return new StandardServletEnvironment();物件的時候,會完成一系列初始化動作,主要就是將執行機器的系統變數和環境變數,加入到其父類AbstractEnvironment定義的物件MutablePropertySources中,MutablePropertySources物件中定義了一個屬性集合:

private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
複製程式碼

執行到這裡,系統變數和環境變數已經被載入到配置檔案的集合中,接下來就行解析專案中的配置檔案。

來看一下listeners.environmentPrepared(environment);,上面已經提到了,這裡是第二次釋出事件。什麼事件呢? 顧名思義,系統環境初始化完成的事件。 釋出事件的流程上面已經講過了,這裡不在贅述。來看一下根據事件型別獲取到的監聽器:

這裡寫圖片描述

可以看到獲取到的監聽器和第一次釋出啟動事件獲取的監聽器有幾個是重複的,這也驗證了監聽器是可以多次獲取,根據事件型別來區分具體處理邏輯。上面介紹日誌監聽器的時候已經提到。 主要來看一下ConfigFileApplicationListener,該監聽器非常核心,主要用來處理專案配置。專案中的 properties 和yml檔案都是其內部類所載入。具體來看一下: 首先方法執行入口:

這裡寫圖片描述

首先還是會去讀spring.factories 檔案,List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();獲取的處理類有以下四種:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=	//一個@FunctionalInterface函式式介面
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,//為springCloud提供的擴充套件類
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,//支援json環境變數
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor //springBoo2提供的一個包裝類,主要將`StandardServletEnvironment`包裝成`SystemEnvironmentPropertySourceEnvironmentPostProcessor`物件
複製程式碼

在執行完上述三個監聽器流程後,ConfigFileApplicationListener會執行該類本身的邏輯。由其內部類Loader載入專案制定路徑下的配置檔案:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
複製程式碼

至此,專案的變數配置已全部載入完畢,來一起看一下:

這裡寫圖片描述
這裡一共6個配置檔案,取值順序由上到下。也就是說前面的配置變數會覆蓋後面同名的配置變數。專案配置變數的時候需要注意這點。


SpringBoot2 | SpringBoot啟動流程原始碼分析(一)

SpringBoot2 | SpringBoot啟動流程原始碼分析(二)

SpringBoot2 | @SpringBootApplication註解 自動化配置流程原始碼分析(三)

SpringBoot2 | SpringBoot Environment原始碼分析(四)

SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)

SpringBoot2 | SpringBoot監聽器原始碼分析 | 自定義ApplicationListener(六)

SpringBoot2 | 條件註解@ConditionalOnBean原理原始碼深度解析(七)

相關文章