P1-1 springboot啟動(dòng)原理

內(nèi)容簡(jiǎn)介:介紹springboot的啟動(dòng)原理

相關(guān)鏈接

背景:SpringBoot?2.4

一、啟動(dòng)文件

SpringBoot是Spring的包裝,通過(guò)自動(dòng)配置使得SpringBoot可以做到開(kāi)箱即用,創(chuàng)建springboot項(xiàng)目之后會(huì)生成xxxApplication.java文件,該文件就是啟動(dòng)文件


run方法就是創(chuàng)建個(gè)spring容器,然后創(chuàng)建個(gè)web容器(tomcat,jetty等)啟動(dòng).

讓我們看到run方法是如何實(shí)現(xiàn)的


又調(diào)用了另一個(gè)run方法


這一步就是創(chuàng)建了SpringApplication對(duì)象并且執(zhí)行了run方法SpringApplication實(shí)例化

?二、SpringApplication實(shí)例化


從上圖SpringApplication實(shí)例化源代碼中可以看出,在SpringApplication實(shí)例初始化的時(shí)候,它主要做這幾件事事情:

? ? (1)-根據(jù)classpath里面是否存在某個(gè)特征類ConfigurableWebApplicationContext來(lái)決定是否應(yīng)該創(chuàng)建一個(gè)為Web應(yīng)用使用的ApplicationContext類型。

? ? (2)-使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的 ApplicationContextInitializer。

? ? (3)-使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationListener。

? ? (4)-推斷并設(shè)置main方法的定義類。

下面來(lái)看下具體的代碼。

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


這里主要是通過(guò)判斷REACTIVE相關(guān)的字節(jié)碼是否存在,如果不存在,則web環(huán)境即為SERVLET類型。這里設(shè)置好web環(huán)境類型,在后面會(huì)根據(jù)類型初始化對(duì)應(yīng)環(huán)境。

2-加載文件getSpringFactoriesInstances方法:

getSpringFactoriesInstances方法會(huì)加載META-INF/spring.factories文件,spring.factories文件中的默認(rèn)的實(shí)現(xiàn)類,此處涉及兩個(gè)類ApplicationListeners和ApplicationContextInitializer。

????(1) ApplicationListener可以監(jiān)聽(tīng)某個(gè)事件event,通過(guò)實(shí)現(xiàn)這個(gè)接口,傳入一個(gè)泛型事件,在run方法中就可以監(jiān)聽(tīng)這個(gè)事件,從而做出一定的邏輯

????(2)ApplicationContextInitializer是spring組件spring-context組件中的一個(gè)接口,主要是spring ioc容器刷新之前的一個(gè)回調(diào)接口,用于處理自定義邏輯。


?二、SpringApplication.run方法

Spring Boot應(yīng)用的整個(gè)啟動(dòng)流程都封裝在SpringApplication.run方法中,本質(zhì)上其實(shí)就是在spring的基礎(chǔ)之上做了封裝,做了大量的擴(kuò)張。我們看下其源碼


該方法中實(shí)現(xiàn)了如下幾個(gè)關(guān)鍵步驟:

?? ?1.創(chuàng)建了應(yīng)用的監(jiān)聽(tīng)器SpringApplicationRunListeners并開(kāi)始監(jiān)聽(tīng)

????2.加載SpringBoot配置環(huán)境(ConfigurableEnvironment),

????3.根據(jù)是否是web項(xiàng)目,來(lái)創(chuàng)建不同的ApplicationContext容器

????4.實(shí)例化SpringBootExceptionReporter.class,用來(lái)支持報(bào)告關(guān)于啟動(dòng)的錯(cuò)誤,

? 5.準(zhǔn)備容器prepareContext方法將listeners、environment、banner、applicationArguments等重要組件與上下文對(duì)象關(guān)聯(lián)

? ?6.刷新容器,refreshContext(context)方法(初始化方法如下)將是實(shí)現(xiàn)spring-boot-starter-*(mybatis、redis等)自動(dòng)化配置的關(guān)鍵,包括spring.factories的加載,bean的實(shí)例化等核心工作。

????7.刷新容器后的擴(kuò)展接口

????回顧整體流程,Springboot的啟動(dòng),主要?jiǎng)?chuàng)建了配置環(huán)境(environment)、事件監(jiān)聽(tīng)(listeners)、應(yīng)用上下文(applicationContext),并基于以上條件,在容器中開(kāi)始實(shí)例化我們需要的Bean,至此,通過(guò)SpringBoot啟動(dòng)的程序已經(jīng)構(gòu)造完成,接下來(lái)我們來(lái)探討具體實(shí)現(xiàn)過(guò)程。

(一)獲取并開(kāi)啟監(jiān)聽(tīng)器

1-獲取監(jiān)聽(tīng)器getRunListeners方法

這個(gè)方法就是去拿到所有的SpringApplicationRunListener實(shí)現(xiàn)類,用于SpringBoot事件發(fā)布的,啟動(dòng)的時(shí)候會(huì)先加載spring會(huì)加載所有jar包下的META-INF/spring.factories,然后緩存起來(lái)


1.1-其中用到了getSpringFactoriesInstances方法:

該方法是獲取spring.factories對(duì)應(yīng)的監(jiān)聽(tīng)器,其在SpringApplication的構(gòu)造方法中調(diào)用了兩次,分別用來(lái)設(shè)置屬性List<ApplicationContextInitializer<?>> initializers和List<ApplicationListener<?>> listeners。getSpringFactoriesInstances在第一次被調(diào)用時(shí)會(huì)將類路徑下所有的META-INF/spring.factories的文件中的屬性進(jìn)行加載并緩存到SpringFactoriesLoader的緩存cache中,下次被調(diào)用的時(shí)候就直接從SpringFactoriesLoader的cache中取數(shù)據(jù)了。這次就是從SpringFactoriesLoader的cache中取SpringApplicationRunListener類型的類(全限定名),然后實(shí)例化后返回。我們來(lái)跟下這次getSpringFactoriesInstances獲取的的內(nèi)容


我們進(jìn)去看一下getSpringFactoriesInstances方法


1.2-上面通過(guò)反射獲取實(shí)例時(shí)會(huì)觸發(fā)EventPublishingRunListener的構(gòu)造函數(shù):



1.3重點(diǎn)來(lái)看一下其中的addApplicationListener方法:


EventPublishingRunListener的構(gòu)造方法中,構(gòu)造了一個(gè)SimpleApplicationEventMulticaster對(duì)象,并將SpringApplication的listeners中的全部listener賦值到SimpleApplicationEventMulticaster對(duì)象的屬性defaultRetriever(類型是ListenerRetriever)的applicationListeners集合中,繼承關(guān)系為:


2-啟動(dòng)監(jiān)聽(tīng)器starting方法

進(jìn)入starting方法,可以看出它對(duì)監(jiān)聽(tīng)器進(jìn)行了遍歷


2-1進(jìn)去listener.starting方法

獲取的監(jiān)聽(tīng)器為EventPublishingRunListener,從名字可以看出是啟動(dòng)事件發(fā)布監(jiān)聽(tīng)器,主要用來(lái)發(fā)布啟動(dòng)事件。


從上圖可以看出構(gòu)建了一個(gè)ApplicationStartingEvent事件,并將其發(fā)布出去,其中調(diào)用了resolveDefaultEventType方法,該方法返回了一個(gè)封裝了事件的默認(rèn)類型(ApplicationStartingEvent)的ResolvableType對(duì)象。我們接著往下看,看看這個(gè)發(fā)布過(guò)程做了些什么。

2-2 multicastEvent方法


這里會(huì)根據(jù)事件類型ApplicationStartingEvent遍歷getApplicationListeners(event, type)獲取對(duì)應(yīng)的監(jiān)聽(tīng)器,在容器啟動(dòng)之后執(zhí)行響應(yīng)的動(dòng)作,對(duì)每個(gè)listener進(jìn)行invokeListener(listener, event)。

2-2-1getApplicationListeners方法

其作用是:返回與給定事件類型匹配的ApplicationListeners集合,非匹配的偵聽(tīng)器會(huì)被提前排除;允許根據(jù)緩存的匹配結(jié)果來(lái)返回。


getApplicationListeners方法主要以下3點(diǎn):

緩存retrieverCache、retrieveApplicationListeners以及retrieveApplicationListeners中調(diào)用的supportsEvent方法。流程是這樣的:

? ?(1)緩存中是否有匹配的結(jié)果,有則返回

???(2)若緩存中沒(méi)有匹配的結(jié)果,則從this.defaultRetriever.applicationListeners中過(guò)濾,這個(gè)this表示的EventPublishingRunListener對(duì)象的屬性initialMulticaster(即SimpleApplicationEventMulticaster對(duì)象,而defaultRetriever.applicationListeners的值也是在EventPublishingRunListener構(gòu)造方法中初始化的)

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

? (4)將過(guò)濾的結(jié)果緩存到retrieverCache

? (5)將過(guò)濾出的結(jié)果返回回去

過(guò)濾出的listener對(duì)象有以下幾種


2-2-2 invokeListener方法

其作用是:使用給定的事件調(diào)用給定的監(jiān)聽(tīng)器


2-3 onApplicationEvent方法

getApplicationListeners方法過(guò)濾出的監(jiān)聽(tīng)器都會(huì)被調(diào)用,過(guò)濾出來(lái)的監(jiān)聽(tīng)器包括LoggingApplicationListener、LiquibaseServiceLocatorApplicationListener、BackgroundPreinitializer、EnableEncryptablePropertiesBeanFactoryPostProcessor、DelegatingApplicationListener、五種類型的對(duì)象。這五個(gè)對(duì)象的onApplicationEvent都會(huì)被調(diào)用。根據(jù)發(fā)布的事件類型從上述監(jiān)聽(tīng)器中選擇對(duì)應(yīng)的監(jiān)聽(tīng)器進(jìn)行事件發(fā)布,這里選了一個(gè) springBoot 的日志監(jiān)聽(tīng)器來(lái)進(jìn)行講解,核心代碼如下:



包含以下五種情況

????(1)在springboot啟動(dòng)的時(shí)候

????(2)springboot的Environment環(huán)境準(zhǔn)備完成的時(shí)候

????(3)在springboot容器的環(huán)境設(shè)置完成以后

????(4)容器關(guān)閉的時(shí)候

????(5)容器啟動(dòng)失敗的時(shí)候

(二)環(huán)境準(zhǔn)備prepareEnvironment方法


2-1 getOrCreateEnvironment方法

前面已經(jīng)提到,environment已經(jīng)被設(shè)置了servlet類型,所以這里創(chuàng)建的是環(huán)境對(duì)象是StandardServletEnvironment。


在返回return new StandardServletEnvironment();對(duì)象的時(shí)候,會(huì)完成一系列初始化動(dòng)作,主要就是將運(yùn)行機(jī)器的系統(tǒng)變量和環(huán)境變量,加入到其父類AbstractEnvironment定義的對(duì)象MutablePropertySources中,MutablePropertySources對(duì)象中定義了一個(gè)屬性集合:private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();

執(zhí)行到這里,系統(tǒng)變量和環(huán)境變量已經(jīng)被載入到配置文件的集合中,接下來(lái)就行解析項(xiàng)目中的配置文件。

2-2 listeners.environmentPrepared方法

進(jìn)入該方法



這里是第二次發(fā)布事件:系統(tǒng)環(huán)境初始化完成的事件。發(fā)布事件的流程上面已經(jīng)講過(guò)了,這里不在贅述。

(三)創(chuàng)建容器createApplicationContext方法


進(jìn)入該方法


上面可以看出,這里創(chuàng)建容器的類型 還是根據(jù)webApplicationType進(jìn)行判斷的,前文已經(jīng)講述了該變量如何賦值的過(guò)程。因?yàn)樵擃愋蜑镾ERVLET類型,所以該容器的名稱為AnnotationConfigServletWebServerApplicationContext

依賴關(guān)系如下圖所示。


(四)準(zhǔn)備容器prepareContext

這一步主要是在容器刷新之前的準(zhǔn)備動(dòng)作。包含一個(gè)非常關(guān)鍵的操作:將啟動(dòng)類注入容器,為后續(xù)開(kāi)啟自動(dòng)化配置奠定基礎(chǔ).


4-1postProcessApplicationContext方法

容器的post處理,子類可以根據(jù)需要申請(qǐng)額外處理。


這里默認(rèn)不執(zhí)行任何邏輯,因?yàn)閎eanNameGenerator和resourceLoader默認(rèn)為空。之所以這樣做,是springBoot留給我們的擴(kuò)展處理方式,類似于這樣的擴(kuò)展,spring中也有很多。

4-2applyInitializers方法

將ApplicationContextInitializer應(yīng)用到上下文


在 applyInitializers 中遍歷調(diào)用每一個(gè)被加載的 ApplicationContextInitializer 的? initialize(context);? 方法,并將 ConfigurableApplicationContext 的實(shí)例傳遞給 initialize 方法。

4-3 listeners.contextPrepared(context)

通知監(jiān)聽(tīng)器,上下文準(zhǔn)備好了,調(diào)用EventPublishingRunListener的contextPrepared,發(fā)現(xiàn)其是空實(shí)現(xiàn),也就是相當(dāng)于啥事也沒(méi)做。

4-4 load加載啟動(dòng)類

這里會(huì)將我們的啟動(dòng)類加載spring容器beanDefinitionMap中,為后續(xù)springBoot 自動(dòng)化配置奠定基礎(chǔ),springBoot為我們提供的各種注解配置也與此有關(guān)。



需要注意的是,springBoot2會(huì)優(yōu)先選擇groovy加載方式,找不到再選用java方式?;蛟Sgroovy動(dòng)態(tài)加載class文件的性能更勝一籌

4-5 listeners.contextLoaded(context)

通知監(jiān)聽(tīng)器,容器已準(zhǔn)備就緒

(五)刷新容器refreshContext(context)

執(zhí)行到這里,springBoot相關(guān)的處理工作已經(jīng)結(jié)束,接下的工作就交給了spring。

實(shí)際上Tomcat的啟動(dòng)也是在refresh流程中,這個(gè)方法其中一步是調(diào)用了onRefresh方法,在Spring中這是一個(gè)沒(méi)有實(shí)現(xiàn)的模板方法,而SpringBoot就通過(guò)這個(gè)方法完成了Tomcat的啟動(dòng):


這里首先拿到TomcatServletWebServerFactory對(duì)象,通過(guò)該對(duì)象再去創(chuàng)建和啟動(dòng)Tomcat:


一路跟進(jìn)去,refresh方法在spring整個(gè)源碼體系中舉足輕重,是實(shí)現(xiàn) ioc 和 aop的關(guān)鍵。上述流程,不是一篇博文能夠展示清楚的,所以這里暫時(shí)不做展開(kāi)。后續(xù)會(huì)有詳細(xì)的介紹。

(六)刷新容器后的擴(kuò)展接口refreshContext

擴(kuò)展接口,設(shè)計(jì)模式中的模板方法,默認(rèn)為空實(shí)現(xiàn)。如果有自定義需求,可以重寫(xiě)該方法。比如打印一些啟動(dòng)結(jié)束log,或者一些其它后置處理。

至此springBoot2啟動(dòng)流程到這里就結(jié)束了,引用一張流程圖。


上圖為SpringBoot啟動(dòng)結(jié)構(gòu)圖,我們發(fā)現(xiàn)啟動(dòng)流程主要分為三個(gè)部分,第一部分進(jìn)行SpringApplication的初始化模塊,配置一些基本的環(huán)境變量、資源、構(gòu)造器、監(jiān)聽(tīng)器,第二部分實(shí)現(xiàn)了應(yīng)用具體的啟動(dòng)方案,包括啟動(dòng)流程的監(jiān)聽(tīng)模塊、加載配置環(huán)境模塊、及核心的創(chuàng)建上下文環(huán)境模塊,第三部分是自動(dòng)化配置模塊,該模塊作為springboot自動(dòng)配置核心,

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容