SpringBoot 版本 : 2.2.1.RELEASE
Spring 版本 : 5.2.1.RELEASE
關(guān)鍵詞:ConfigFileApplicationListener
鳍置,EnvironmentPostProcessor
删壮,SprngBoot啟動順序源碼解讀
注:本文通過源碼指出 Spring & SpringBoot 相關(guān)配置加載順序缤谎,如有異議许起,歡迎下方評論
- 相信用SpringBoot框架做開發(fā)的時候切平,很多人腦海中會浮現(xiàn)這樣一個問題:框架內(nèi)得這么多配置都是什么時候加載得呢?順序又是怎樣的呢圆恤? 帶著這樣得問題音比,本篇將會給出具體得相關(guān)配置源碼所在得位置。
- ??了解了SpringBoot啟動順序隧哮,本篇閱讀起來會更加流暢??桶良。推薦文集:
SpringBoot啟動 源碼深度解析(一)
SpringBoot啟動 源碼深度解析(二)
SpringBoot啟動 源碼深度解析(三)
SpringBoot啟動 源碼深度解析(四)
1. 加載SpringBoot項目默認(rèn)配置文件
1.1 參考Spring官網(wǎng) 章節(jié) 2.3. Application Property Files
application.properties(yml)
優(yōu)先級從高到低:
- 項目根目錄下 config 目錄下得配置文件
- 項目根目錄下得配置文件
- resources下面cofnig目錄下得配置文件
- resources下面得配置文件
總結(jié):
1. 同一目錄級別下,帶有config目錄得優(yōu)先級高于不帶有config目錄得配置
2. 項目根目錄下配置屬性得優(yōu)先級高于resources目錄
3. 同級目錄下properties優(yōu)先于yml文件
-
源碼位置:ConfigFileApplicationListenerspring.properties
2. 外部化配置(重點)
2.1 參考Spring官網(wǎng) 章節(jié) 2. Externalized Configuration
Externalized Configuration
- Devtools主目錄得全局設(shè)置屬性(前提是要激活Devtools)
- 帶有@TestPropertySource得注解配置
- 帶有@SpringBootTest上注解配置
- 命令行參數(shù):SpringBoot啟動時近迁,通過 org.springframework.boot.SpringApplication#configurePropertySources(ConfigurableEnvironment environment, String[] args)設(shè)置得命令行參數(shù)
艺普,然后會向?qū)傩灾刑砑淤Y源SimpleCommandLinePropertySource對象,而該對象得構(gòu)造中鉴竭,會調(diào)用org.springframework.core.env.SimpleCommandLineArgsParser#parse方法解析命令行參數(shù)SpringBoot啟動時 configurePropertySourcesSimpleCommandLinePropertySource構(gòu)造器SimpleCommandLineArgsParser命令行參數(shù)解析類
??這不正是我們服務(wù)啟動時( java -jar --server.port=8080)經(jīng)常用到得命令格式嗎??- 嵌入在環(huán)境得Json屬性:此處有個很重要得監(jiān)聽器
ConfigFileApplicationListener
歧譬,該監(jiān)聽器會監(jiān)聽兩個事件:ApplicationEnvironmentPreparedEvent
與ApplicationPreparedEvent
,而ApplicationEnvironmentPreparedEvent事件
會在準(zhǔn)備好環(huán)境之后(見圖6
)調(diào)用代碼listeners.environmentPrepared(environment)發(fā)出.此時ConfigFileApplicationListener
就會監(jiān)聽到事件搏存,執(zhí)行onApplicationEvent回調(diào)監(jiān)聽
瑰步,首先從spring.properties中加載圖5EnvironmentPostProcessor實例
并調(diào)用實例得postProcessEnvironment方法,spring.factories
此時我們發(fā)現(xiàn)其中有個實例為SpringApplicationJsonEnvironmentPostProcessor
璧眠,進(jìn)到類中:SpringApplicationJsonEnvironmentPostProcessorJsonPropertyValue
會在postProcessEnvironment
方法中處理環(huán)境中得屬性spring.application.json(優(yōu)先)
與SPRING_APPLICATION_JSON
缩焦,若不為空,則調(diào)用processJson
方法解析json值责静,袁滥,解析之后會調(diào)用processJson方法與addJsonPropertySource方法addJsonPropertySource
方法執(zhí)行具體得添加操作,首先調(diào)用findPropertySource()
findPropertySource方法??其實作者一直對官網(wǎng)給得順序有質(zhì)疑灾螃,之前作者一直認(rèn)為servletConfigInitParams题翻,servletContextInitParams,jndiProperties這三個屬性在json處理之前腰鬼,但是看到這個findPropertySource方法之后才豁然開朗嵌赠,如果你也有類時得同感,我認(rèn)為你對SpringBoot啟動順序以及相關(guān)底層源碼已經(jīng)有一定得了解了熄赡。??
- servletConfigInitParams姜挺;servletContextInitParams;jndiProperties:這些參數(shù)是創(chuàng)建Servlet環(huán)境時添加得屬性(
重要得事情再說一遍彼硫,SpringBoot啟動順序很重要
)
圖6創(chuàng)建標(biāo)準(zhǔn)Servlet環(huán)境StandardServletEnvironment
參數(shù)順序設(shè)置在StandardServletEnvironment類中炊豪。方法中還有調(diào)用 super.customizePropertySources(propertySources)凌箕,執(zhí)行父類得自定義屬性資源添加方法:StandardEnvironment- 細(xì)心得讀者會發(fā)現(xiàn)
ConfigFileApplicationListener
也是EnvironmentPostProcessor得實例,但是為什么不在spring.factories中呢词渤?我們仔細(xì)觀察圖5
得方法發(fā)現(xiàn)有一段代碼是postProcessors.add(this)
頓時恍然大悟陌知,此操作會將自己添加到最后一個位置去處理postProcessEnvironment
方法,而ConfigFileApplicationListener
得處理邏輯:標(biāo)紅得兩處重點分析:ConfigFileApplicationListener得postProcessEnvironment方法將隨機屬性得addToEnvironment方法random屬性添加到systemEnvironment之后
- 第二處是Loader類得load方法:首先會調(diào)用構(gòu)造器:
內(nèi)部類Loader得構(gòu)造器
其中幾行很重要得代碼:首先初始化屬性占位符解析器掖肋,然后賦值資源加載器,最后在spring.factories中獲取PropertySourceLoader實例
:看名字我們大概可以猜到是對spring.factories中得內(nèi)置PropertySourceLoaderproperties赏参、yaml文件
得解析:PropertiesPropertySourceLoader發(fā)現(xiàn)還可以解析YamlPropertySourceLoaderxml與yml
志笼,所以總共可以解析得文件類型包括properties、xml把篓、yml纫溃、yaml
。然后就是load方法實現(xiàn)屬性配置得加載:
內(nèi)部類Loader得load方法static資源初始化LOAD_FILTERED_PROPERTY屬性
方法內(nèi)部調(diào)用了FilteredPropertySource得apply方法
:FilteredPropertySource得apply方法
若環(huán)境參數(shù)包含當(dāng)前屬性韧掩,那么首先獲取原始得屬性資源紊浩,若不為空,新生成一個FilteredPropertySource對象替換掉之前得屬性資源對象疗锐,然后Comsumer消費掉原始資源(Consumer坊谁,函數(shù)式編程)。
??然后lambada里面得邏輯是:
1. 首先調(diào)用 initializeProfiles初始化默認(rèn)得Profile滑臊,沒有設(shè)置環(huán)境得話就是默認(rèn)得 application.properties和application-default.properties口芍,設(shè)置了就是application-${profile}.properties得格式
2. 然后While 循環(huán)取出Profile進(jìn)行判斷是否是默認(rèn)得Profile(默認(rèn)創(chuàng)建得都是false)
內(nèi)部類Profile3. 接著調(diào)用
load
getSearchLocations方法首先會去查找默認(rèn)得屬性spring.config.location,沒有則會去找 spring.config.additional-location屬性
getSearchLocations若spring.config.additional-location屬性也沒有雇卷,則使用默認(rèn)得位置 classpath:/,classpath:/config/,file:./,file:./config/(優(yōu)先級由低到高)
:默認(rèn)搜索locations
getSearchNames先判斷環(huán)境中是否包含spring.config.name屬性鬓椭,若包含獲取對應(yīng)得屬性值解析成Set集(屬性可以有占位符,可以通過逗號分隔)关划,若不包含則使用成員變量names作為屬性值(可以通過setSearchNames方法設(shè)置),默認(rèn)值為application小染,也就是說getSearchNames方法是獲取得不帶Profile得文件名稱。
4. 然后執(zhí)行具體得load加載,循環(huán)判斷 getSearchNames解析出來得name贮折,(1)若值為空裤翩,遍歷構(gòu)造器中解析出來得屬性資源加載器 this.propertySourceLoaders,調(diào)用canLoadFileExtension方法去判斷 getSearchLocations返回得location與任意文件擴展名相同即可(此時配置得只能是文件擴展名脱货,不能為目錄)岛都,然后執(zhí)行具體得資源加載
canLoadFileExtension
(2)若不為空(正常情況下都不會為空),同樣遍歷 this.propertySourceLoaders振峻,獲取支持得擴展名臼疫,調(diào)用 loadForFileExtension方法補全配置文件名并加載具體得屬性配置
loadForFileExtension
首先構(gòu)造默認(rèn)得配置(不含有Profile得,例如:application.properties)扣孟,再構(gòu)造一個帶有Profile得配置烫堤。判斷Profile若不為空,則拼接完整文件路徑調(diào)用 load方法加載(此處得load方法就是具體得資源加載了,通過Spring得抽象Resource接口讀取資源文件鸽斟。Profile為空則不拼接Profle到全路徑中拔创,同理調(diào)用load方法進(jìn)行資源加載)
具體資源讀取方法
- 到此,官網(wǎng)中給出得常用配置順序基本已經(jīng)分析完畢富蓄,其中還有一塊綁定配置也很重要剩燥,有時間作者也會整理出相應(yīng)得源碼與大家一起分享與探討
總結(jié):
1. ??根據(jù)SpringBoot啟動時得順序??,通過源碼找到對應(yīng)得位置有助于加深印象立倍,對啟動源碼熟悉得話更有助于讀者記憶相關(guān)順序
2. 其次就是源碼中使用了函數(shù)式編程灭红,大量得lambada表達(dá)式,若對lambada不是很了解得同學(xué)可以先把這塊得基礎(chǔ)打好口注,那么閱讀起來源碼會更加得心應(yīng)手
3. 另外变擒,配置依托于環(huán)境
,配置得屬性相關(guān)設(shè)置都會保存在環(huán)境下面得緩存中
- ? 文章要是勘誤或者知識點說的不正確寝志,歡迎評論娇斑,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽材部!
- ? 要是感覺文章對你有所幫助毫缆,不妨點個關(guān)注,或者移駕看一下作者的其他文集败富,也都是干活多多哦悔醋,文章也在全力更新中。
- ? 著作權(quán)歸作者所有兽叮。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)芬骄,非商業(yè)轉(zhuǎn)載請注明出處!