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

概述:

前陣子看到了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檔案中的實現類:

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

這裡監聽器為9個:

# Application Listenersorg。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 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 Listenersorg。springframework。boot。SpringApplicationRunListener=\org。springframework。boot。context。event。EventPublishingRunListener

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

@SuppressWarnings(“unchecked”) private List createSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set names) { List 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中。 繼承關係如下:

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

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種監聽器:

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

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

private final List> propertySourceList = new CopyOnWriteArrayList>();

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

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

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

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

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

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

# Environment Post Processorsorg。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/”;

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

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

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

編輯不易,希望看到的小夥伴多多轉發,收藏,給小編一個 關注!感謝您的閱讀,下面小編給大家準備了一份(spring,jvm等的面試資料技術文件)免費分享:

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

如果你想成為一名優秀的 Java 工程師,那麼這份手冊上的內容幾乎是必須要掌握的。如果你想獲取這份學習資料的小夥伴,

關注小編,轉發,收藏文章,私信[高併發]即可獲得獲取方式!