一篇文章讓你看懂SpringBoot配置順序及底層實現(xiàn)

SpringBoot 版本 : 2.2.1.RELEASE
Spring 版本 : 5.2.1.RELEASE
關(guān)鍵詞ConfigFileApplicationListener鳍置,EnvironmentPostProcessor删壮,SprngBoot啟動順序源碼解讀
注:本文通過源碼指出 Spring & SpringBoot 相關(guān)配置加載順序缤谎,如有異議许起,歡迎下方評論

1. 加載SpringBoot項目默認(rèn)配置文件

1.1 參考Spring官網(wǎng) 章節(jié) 2.3. Application Property Files
application.properties(yml)

優(yōu)先級從高到低:

  1. 項目根目錄下 config 目錄下得配置文件
  2. 項目根目錄下得配置文件
  3. resources下面cofnig目錄下得配置文件
  4. resources下面得配置文件

總結(jié):
1. 同一目錄級別下,帶有config目錄得優(yōu)先級高于不帶有config目錄得配置
2. 項目根目錄下配置屬性得優(yōu)先級高于resources目錄
3. 同級目錄下properties優(yōu)先于yml文件

  • 源碼位置:
    ConfigFileApplicationListener
    此類是在META-INF下得spring.properties文件中定義得
    spring.properties

2. 外部化配置(重點)

2.1 參考Spring官網(wǎng) 章節(jié) 2. Externalized Configuration
Externalized Configuration
  1. Devtools主目錄得全局設(shè)置屬性(前提是要激活Devtools)
  2. 帶有@TestPropertySource得注解配置
  3. 帶有@SpringBootTest上注解配置
  4. 命令行參數(shù):SpringBoot啟動時近迁,通過 org.springframework.boot.SpringApplication#configurePropertySources(ConfigurableEnvironment environment, String[] args)設(shè)置得命令行參數(shù)
    SpringBoot啟動時 configurePropertySources
    艺普,然后會向?qū)傩灾刑砑淤Y源SimpleCommandLinePropertySource對象,而該對象得構(gòu)造中鉴竭,會調(diào)用org.springframework.core.env.SimpleCommandLineArgsParser#parse方法解析命令行參數(shù)
    SimpleCommandLinePropertySource構(gòu)造器
    SimpleCommandLineArgsParser命令行參數(shù)解析類

    ??這不正是我們服務(wù)啟動時( java -jar --server.port=8080)經(jīng)常用到得命令格式嗎??
  5. 嵌入在環(huán)境得Json屬性:此處有個很重要得監(jiān)聽器ConfigFileApplicationListener歧譬,該監(jiān)聽器會監(jiān)聽兩個事件:ApplicationEnvironmentPreparedEventApplicationPreparedEvent,而ApplicationEnvironmentPreparedEvent事件會在準(zhǔn)備好環(huán)境之后(見圖6)調(diào)用代碼listeners.environmentPrepared(environment)發(fā)出.此時ConfigFileApplicationListener就會監(jiān)聽到事件搏存,執(zhí)行onApplicationEvent回調(diào)監(jiān)聽
    圖5
    瑰步,首先從spring.properties中加載EnvironmentPostProcessor實例并調(diào)用實例得postProcessEnvironment方法,
    spring.factories

    此時我們發(fā)現(xiàn)其中有個實例為SpringApplicationJsonEnvironmentPostProcessor璧眠,進(jìn)到類中:
    SpringApplicationJsonEnvironmentPostProcessor
    JsonPropertyValue

    會在postProcessEnvironment方法中處理環(huán)境中得屬性spring.application.json(優(yōu)先)SPRING_APPLICATION_JSON缩焦,若不為空,則調(diào)用processJson方法解析json值责静,
    processJson方法與addJsonPropertySource方法
    袁滥,解析之后會調(diào)用addJsonPropertySource方法執(zhí)行具體得添加操作,首先調(diào)用findPropertySource()
    findPropertySource方法
    ??其實作者一直對官網(wǎng)給得順序有質(zhì)疑灾螃,之前作者一直認(rèn)為servletConfigInitParams题翻,servletContextInitParams,jndiProperties這三個屬性在json處理之前腰鬼,但是看到這個findPropertySource方法之后才豁然開朗嵌赠,如果你也有類時得同感,我認(rèn)為你對SpringBoot啟動順序以及相關(guān)底層源碼已經(jīng)有一定得了解了熄赡。??
  6. 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
  7. 細(xì)心得讀者會發(fā)現(xiàn)ConfigFileApplicationListener也是EnvironmentPostProcessor得實例,但是為什么不在spring.factories中呢词渤?我們仔細(xì)觀察圖5得方法發(fā)現(xiàn)有一段代碼是postProcessors.add(this)頓時恍然大悟陌知,此操作會將自己添加到最后一個位置去處理postProcessEnvironment方法,而ConfigFileApplicationListener得處理邏輯:
    ConfigFileApplicationListener得postProcessEnvironment方法
    標(biāo)紅得兩處重點分析
    隨機屬性得addToEnvironment方法
    random屬性添加到systemEnvironment之后
  8. 第二處是Loader類得load方法首先會調(diào)用構(gòu)造器
    內(nèi)部類Loader得構(gòu)造器

    其中幾行很重要得代碼:首先初始化屬性占位符解析器掖肋,然后賦值資源加載器,最后在spring.factories中獲取PropertySourceLoader實例
    spring.factories中得內(nèi)置PropertySourceLoader
    看名字我們大概可以猜到是對properties赏参、yaml文件得解析:
    PropertiesPropertySourceLoader
    YamlPropertySourceLoader
    發(fā)現(xiàn)還可以解析xml與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)部類Profile
    3. 接著調(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)境下面得緩存中

  1. ? 文章要是勘誤或者知識點說的不正確寝志,歡迎評論娇斑,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽材部!
  2. ? 要是感覺文章對你有所幫助毫缆,不妨點個關(guān)注,或者移駕看一下作者的其他文集败富,也都是干活多多哦悔醋,文章也在全力更新中。
  3. ? 著作權(quán)歸作者所有兽叮。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)芬骄,非商業(yè)轉(zhuǎn)載請注明出處!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹦聪,一起剝皮案震驚了整個濱河市账阻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泽本,老刑警劉巖淘太,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異规丽,居然都是意外死亡蒲牧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門赌莺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冰抢,“玉大人,你說我怎么就攤上這事艘狭】嫒牛” “怎么了翠订?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長遵倦。 經(jīng)常有香客問我尽超,道長,這世上最難降的妖魔是什么梧躺? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任似谁,我火速辦了婚禮,結(jié)果婚禮上掠哥,老公的妹妹穿的比我還像新娘棘脐。我一直安慰自己,他們只是感情好龙致,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著顷链,像睡著了一般目代。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嗤练,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天榛了,我揣著相機與錄音,去河邊找鬼煞抬。 笑死霜大,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的革答。 我是一名探鬼主播战坤,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼残拐!你這毒婦竟也來了途茫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤溪食,失蹤者是張志新(化名)和其女友劉穎囊卜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體错沃,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡栅组,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了枢析。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玉掸。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖登疗,靈堂內(nèi)的尸體忽然破棺而出排截,到底是詐尸還是另有隱情嫌蚤,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布断傲,位于F島的核電站脱吱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏认罩。R本人自食惡果不足惜箱蝠,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垦垂。 院中可真熱鬧宦搬,春花似錦、人聲如沸劫拗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽页慷。三九已至憔足,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酒繁,已是汗流浹背滓彰。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留州袒,地道東北人揭绑。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像郎哭,于是被迫代替她去往敵國和親他匪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359