P1-1 springboot啟動原理

內容簡介:介紹springboot的啟動原理

相關連結

背景

:SpringBoot 2。4

一、啟動檔案

SpringBoot是Spring的包裝,透過自動配置使得SpringBoot可以做到開箱即用,建立springboot專案之後會生成xxxApplication。java檔案,該檔案就是啟動檔案

P1-1 springboot啟動原理

run方法就是建立個spring容器,然後建立個web容器(tomcat,jetty等)啟動。

讓我們看到run方法是如何實現的

P1-1 springboot啟動原理

又呼叫了另一個run方法

P1-1 springboot啟動原理

這一步就是建立了SpringApplication物件並且執行了run方法SpringApplication例項化

二、SpringApplication例項化

P1-1 springboot啟動原理

從上圖SpringApplication例項化原始碼中可以看出,在SpringApplication例項初始化的時候,它主要做這幾件事情:

(1)-根據classpath裡面是否存在某個特徵類ConfigurableWebApplicationContext來決定是否應該建立一個為Web應用使用的ApplicationContext型別。

(2)-使用SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationContextInitializer。

(3)-使用SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationListener。

(4)-推斷並設定main方法的定義類。

下面來看下具體的程式碼。

1-環境判斷的deduceWebEnvironment()方法:

P1-1 springboot啟動原理

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

2-載入檔案getSpringFactoriesInstances方法:

getSpringFactoriesInstances方法會載入META-INF/spring。factories檔案,spring。factories檔案中的預設的實現類,此處涉及兩個類ApplicationListeners和ApplicationContextInitializer。

(1) ApplicationListener可以監聽某個事件event,透過實現這個介面,傳入一個泛型事件,在run方法中就可以監聽這個事件,從而做出一定的邏輯

(2)ApplicationContextInitializer是spring元件spring-context元件中的一個介面,主要是spring ioc容器重新整理之前的一個回撥介面,用於處理自定義邏輯。

P1-1 springboot啟動原理

二、SpringApplication.run方法

Spring Boot應用的整個啟動流程都封裝在SpringApplication。run方法中,本質上其實就是在spring的基礎之上做了封裝,做了大量的擴張。我們看下其原始碼

P1-1 springboot啟動原理

該方法中實現瞭如下幾個關鍵步驟:

1。建立了應用的監聽器SpringApplicationRunListeners並開始監聽。

2。載入SpringBoot配置環境(ConfigurableEnvironment)。

3。根據是否是web專案,來建立不同的ApplicationContext容器。

4。例項化SpringBootExceptionReporter。class,用來支援報告關於啟動的錯誤。

5。準備容器prepareContext方法將listeners、environment、banner、applicationArguments等重要 元件與上下文物件關聯。

6。重新整理容器,refreshContext(context)方法(初始化方法如下)將是實現spring-boot-starter-*(mybati s、redis等)自動化配置的關鍵,包括spring。factories的載入,bean的例項化等核心工作。

7。重新整理容器後的擴充套件介面。

回顧整體流程,Springboot的啟動,主要建立了配置環境(environment)、事件監聽(listeners)、應用上下文(applicationContext),並基於以上條件,在容器中開始例項化我們需要的Bean,至此,透過SpringBoot啟動的程式已經構造完成,接下來我們來探討具體實現過程。

(一)獲取並開啟監聽器

1-獲取監聽器getRunListeners方法

這個方法就是去拿到所有的SpringApplicationRunListener實現類,用於SpringBoot事件釋出的,啟動的時候會先載入spring會載入所有jar包下的META-INF/spring。factories,然後快取起來。

P1-1 springboot啟動原理

1。1-其中用到了getSpringFactoriesInstances方法:

該方法是獲取spring。factories對應的監聽器,其在SpringApplication的構造方法中呼叫了兩次,分別用來設定屬性List> initializers和List> listeners。getSpringFactoriesInstances在第一次被呼叫時會將類路徑下所有的META-INF/spring。factories的檔案中的屬性進行載入並快取到SpringFactoriesLoader的快取cache中,下次被呼叫的時候就直接從SpringFactoriesLoader的cache中取資料了。這次就是從SpringFactoriesLoader的cache中取SpringApplicationRunListener型別的類(全限定名),然後例項化後返回。我們來跟下這次getSpringFactoriesInstances獲取的的內容

P1-1 springboot啟動原理

我們進去看一下getSpringFactoriesInstances方法

P1-1 springboot啟動原理

1。2-上面透過反射獲取例項時會觸發EventPublishingRunListener的建構函式:

P1-1 springboot啟動原理

P1-1 springboot啟動原理

1。3重點來看一下其中的addApplicationListener方法:

P1-1 springboot啟動原理

EventPublishingRunListener的構造方法中,構造了一個SimpleApplicationEventMulticaster物件,並將SpringApplication的listeners中的全部listener賦值到SimpleApplicationEventMulticaster物件的屬性defaultRetriever(型別是ListenerRetriever)的applicationListeners集合中,繼承關係為:

P1-1 springboot啟動原理

2-啟動監聽器starting方法

進入starting方法,可以看出它對監聽器進行了遍歷

P1-1 springboot啟動原理

2-1進去listener。starting方法

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

P1-1 springboot啟動原理

從上圖可以看出構建了一個ApplicationStartingEvent事件,並將其釋出出去,其中呼叫了resolveDefaultEventType方法,該方法返回了一個封裝了事件的預設型別(ApplicationStartingEvent)的ResolvableType物件。我們接著往下看,看看這個釋出過程做了些什麼。

2-2 multicastEvent方法

P1-1 springboot啟動原理

這裡會根據事件型別ApplicationStartingEvent遍歷getApplicationListeners(event, type)獲取對應的監聽器,在容器啟動之後執行響應的動作,對每個listener進行invokeListener(listener, event)。

2-2-1getApplicationListeners方法

其作用是:返回與給定事件型別匹配的ApplicationListeners集合,非匹配的偵聽器會被提前排除;允許根據快取的匹配結果來返回。

P1-1 springboot啟動原理

getApplicationListeners方法主要以下3點:

快取retrieverCache、retrieveApplicationListeners以及retrieveApplicationListeners中呼叫的supportsEvent方法。流程是這樣的:

(1)快取中是否有匹配的結果,有則返回

(2)若快取中沒有匹配的結果,則從this。defaultRetriever。applicationListeners中過濾,這個this表示的EventPublishingRunListener物件的屬性initialMulticaster(即SimpleApplicationEventMulticaster物件,而defaultRetriever。applicationListeners的值也是在EventPublishingRunListener構造方法中初始化的)

(3)過濾過程,遍歷defaultRetriever。applicationListeners集合,從中找出ApplicationStartingEvent匹配的listener,具體的匹配規則需要看各個listener的supportsEventType方法(有兩個過載的方法)

(4)將過濾的結果快取到retrieverCache

(5)將過濾出的結果返回回去

過濾出的listener物件有以下幾種

P1-1 springboot啟動原理

2-2-2 invokeListener方法

其作用是:使用給定的事件呼叫給定的監聽器

P1-1 springboot啟動原理

2-3 onApplicationEvent方法

getApplicationListeners方法過濾出的監聽器都會被呼叫,過濾出來的監聽器包括LoggingApplicationListener、BackgroundPreinitializer、DelegatingApplicationListener、LiquibaseServiceLocatorApplicationListener、EnableEncryptablePropertiesBeanFactoryPostProcessor五種型別的物件。這五個物件的onApplicationEvent都會被呼叫。

根據釋出的事件型別從上述監聽器中選擇對應的監聽器進行事件釋出,這裡選了一個 springBoot 的日誌監聽器來進行講解,核心程式碼如下:

P1-1 springboot啟動原理

P1-1 springboot啟動原理

包含以下五種情況

(1)在springboot啟動的時候

(2)springboot的Environment環境準備完成的時候

(3)在springboot容器的環境設定完成以後

(4)容器關閉的時候

(5)容器啟動失敗的時候

(二)環境準備prepareEnvironment方法

P1-1 springboot啟動原理

1- getOrCreateEnvironment方法

前面已經提到,environment已經被設定了servlet型別,所以這裡建立的是環境物件是StandardServletEnvironment。

P1-1 springboot啟動原理

在返回return new StandardServletEnvironment();物件的時候,會完成一系列初始化動作,主要就是將執行機器的系統變數和環境變數,加入到其父類AbstractEnvironment定義的物件MutablePropertySources中,MutablePropertySources物件中定義了一個屬性集合:private final List> propertySourceList = new CopyOnWriteArrayList>();

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

2- listeners。environmentPrepared方法

進入該方法

P1-1 springboot啟動原理

P1-1 springboot啟動原理

這裡是第二次釋出事件:系統環境初始化完成的事件。釋出事件的流程上面已經講過了,這裡不再贅述。

(三)建立容器createApplicationContext方法

P1-1 springboot啟動原理

進入該方法

P1-1 springboot啟動原理

上面可以看出,這裡建立容器的型別 還是根據webApplicationType進行判斷的,前文已經講述了該變數如何賦值的過程。因為該型別為SERVLET型別,所以該容器的名稱為AnnotationConfigServletWebServerApplicationContext

依賴關係如下圖所示。

P1-1 springboot啟動原理

(四)準備容器prepareContext

這一步主要是在容器重新整理之前的準備動作。包含一個非常關鍵的操作:將啟動類注入容器,為後續開啟自動化配置奠定基礎。

P1-1 springboot啟動原理

1-postProcessApplicationContext方法

容器的post處理,子類可以根據需要申請額外處理。

P1-1 springboot啟動原理

這裡預設不執行任何邏輯,因為beanNameGenerator和resourceLoader預設為空。之所以這樣做,是springBoot留給我們的擴充套件處理方式,類似於這樣的擴充套件,spring中也有很多。

2-applyInitializers方法

將ApplicationContextInitializer應用到上下文

P1-1 springboot啟動原理

在applyInitializers 中遍歷呼叫每一個被載入的 ApplicationContextInitializer 的 initialize(context); 方法,並將 ConfigurableApplicationContext 的例項傳遞給 initialize 方法。

3- listeners。contextPrepared(context)

通知監聽器,上下文準備好了,呼叫EventPublishingRunListener的contextPrepared,發現其是空實現,也就是相當於啥事也沒做。

4- load載入啟動類

這裡會將我們的啟動類載入spring容器beanDefinitionMap中,為後續springBoot 自動化配置奠定基礎,springBoot為我們提供的各種註解配置也與此有關。

P1-1 springboot啟動原理

P1-1 springboot啟動原理

需要注意的是,springBoot2會優先選擇groovy載入方式,找不到再選用java方式。或許groovy動態載入class檔案的效能更勝一籌。

5- listeners。contextLoaded(context)

通知監聽器,容器已準備就緒。

(五)重新整理容器refreshContext(context)

執行到這裡,springBoot相關的處理工作已經結束,接下的工作就交給了spring。

實際上Tomcat的啟動也是在refresh流程中,這個方法其中一步是呼叫了onRefresh方法,在Spring中這是一個沒有實現的模板方法,而SpringBoot就透過這個方法完成了Tomcat的啟動:

P1-1 springboot啟動原理

這裡首先拿到TomcatServletWebServerFactory物件,透過該物件再去建立和啟動Tomcat:

P1-1 springboot啟動原理

一路跟進去,refresh方法在spring整個原始碼體系中舉足輕重,是實現 ioc 和 aop的關鍵。上述流程,不是一篇博文能夠展示清楚的,所以這裡暫時不做展開。後續會有詳細的介紹。

(六)重新整理容器後的擴充套件介面refreshContext擴充套件介面

設計模式中的模板方法,預設為空實現。如果有自定義需求,可以重寫該方法。比如列印一些啟動結束log,或者一些其它後置處理。至此springBoot2啟動流程到這裡就結束了,引用一張流程圖。

P1-1 springboot啟動原理

上圖為SpringBoot啟動結構圖,我們發現啟動流程主要分為三個部分,第一部分進行SpringApplication的初始化模組,配置一些基本的環境變數、資源、構造器、監聽器,第二部分實現了應用具體的啟動方案,包括啟動流程的監聽模組、載入配置環境模組、及核心的建立上下文環境模組,第三部分是自動化配置模組,該模組作為springboot自動配置核心,