SpringBoot加載配置文件原理分析

本文是基于SpringBoot2.4.0之前的版本分析的,2.4.0之后有所不同

想必大家對(duì)SpringBoot中的application.properties(或application.yaml)文件都是再熟悉不過(guò)的了。它是應(yīng)用的配置文件,我們可以把需要的一些配置信息都寫(xiě)在這個(gè)文件里面,需要的時(shí)候,我們可以通過(guò)@Value注解來(lái)直接獲取即可,那這個(gè)文件是什么時(shí)候以及如何被應(yīng)該加載的呢?這一直是我之前疑惑的地方,也是終于有時(shí)間對(duì)這一部分進(jìn)行了詳細(xì)的源碼分析。

原理先行

整個(gè)配置文件的加載過(guò)程其實(shí)就是一個(gè)事件監(jiān)聽(tīng)的機(jī)制來(lái)實(shí)現(xiàn)的,整個(gè)過(guò)程的主要流程如下:

springboot啟動(dòng)讀取配置文件的總體流程.png

在SpringBoot啟動(dòng)的時(shí)候,首先需要?jiǎng)?chuàng)建SpringApplication對(duì)象,而就在創(chuàng)建這個(gè)對(duì)象的時(shí)候,會(huì)讀取spring.factories配置文件獲取其中的所有配置的Application Listeners的全類名,并創(chuàng)建這些監(jiān)聽(tīng)器的對(duì)象,存放到SpringApplication的屬性listeners中,在這其中就有一個(gè)ConfigFileApplicationListener監(jiān)聽(tīng)器(如下圖),這個(gè)就是和讀取配置文件相關(guān)的一個(gè)監(jiān)聽(tīng)器;好了,當(dāng)ConfigFileApplicationListener這個(gè)監(jiān)聽(tīng)器創(chuàng)建好了之后,就開(kāi)始監(jiān)聽(tīng)它可以處理的兩個(gè)事件了【ApplicationEnvironmentPreparedEvent/ApplicationPreparedEvent】。

spring.factories中的ConfigFileApplicationListener.png

當(dāng)SpringApplication對(duì)象創(chuàng)建完成之后就會(huì)調(diào)用該對(duì)象的run方法,run方法中準(zhǔn)備環(huán)境的方法prepareEnvironment中發(fā)布了一個(gè)ApplicationEnvironmentPreparedEvent事件。睜大眼睛看下,這個(gè)事件不就是前面創(chuàng)建的ConfigFileApplicationListener正在監(jiān)聽(tīng)可以處理的事件嗎?所以后面讀取配置文件的邏輯就順利成章的走了起來(lái);這就叫什么:你需要什么,我剛好有,我給你唄。

詳細(xì)的處理流程

如果我直接從源碼一點(diǎn)一點(diǎn)的來(lái)分析,感覺(jué)好無(wú)聊啊,我覺(jué)得你們肯定也會(huì)這么想的。既然配置文件的使用我們已經(jīng)熟能生巧了,那我們帶著我們知道的知識(shí)點(diǎn)去來(lái)看源碼那不是更好嗎,這就叫:知其然更要知其所以然。

1、為什么我們的配置文件既可以以properties結(jié)尾,又可以以yml,yaml,以及以前的xml結(jié)尾呢?

其實(shí)這個(gè)我們盲猜也知道,這肯定是通過(guò)不同的加載器來(lái)進(jìn)行不同的后綴進(jìn)行加載的。如果你能想到這就不錯(cuò)了,我們來(lái)一起看看源碼里是怎么處理的:在底層構(gòu)建加載的Loader類的時(shí)候,SpringBoot就從spring.factories的配置文件中讀取到了兩種屬性資源加載器并進(jìn)行了實(shí)例化(如下圖),即:PropertiesPropertySourceLoader、YamlPropertySourceLoader;

實(shí)例化配置的屬性資源加載器.png

那就接著看一看這兩個(gè)屬性資源加載器分別能處理哪個(gè)配置文件以及是如何發(fā)揮作用的(如下圖):看代碼分析,SpringBoot會(huì)在找好配置文件的名稱以及profile后,通過(guò)遍歷所有的屬性資源加載器來(lái)拼接后綴,然后再去指定的路徑下進(jìn)行加載這些文件,沒(méi)有的話就直接跳過(guò)continue。


PropertiesPropertySourceLoader.png
YamlPropertySourceLoader.png
屬性資源加載器的處理.png

2、為什么我們使用application做為配置文件的名稱,它就可以加載到呢?我們可以自己指定配置文件的名稱嗎?

我們使用SpringBoot官網(wǎng)自帶的腳手架生成項(xiàng)目的時(shí)候,默認(rèn)給我們生成的就:application.properties,這顯然后面底層SpringBoot默認(rèn)的配置文件名稱就是application,我們看下底層的代碼(如下):在獲取配置文件的名字的時(shí)候,SpringBoot底層首先會(huì)在environment中查找spring.config.name這個(gè)配置,如果有的話,就會(huì)返回spring.config.name配置的信息作為配置文件的名字,而如果沒(méi)有配置spring.config.name的話,就會(huì)使用默認(rèn)的application作為配置文件的名稱。如果我們需要指定特性的名字作為配置文件的名字,那我們只需要在啟動(dòng)之前配上spring.config.name即可。

獲取配置文件的名字代碼邏輯.png

3、SpringBoot底層是如何實(shí)現(xiàn)不同環(huán)境配置文件的切換(激活)的呢?

我們知道在SpringBoot啟動(dòng)的時(shí)候是先加載application.properties,然后在加載我們配置的spring.profiles.active指定環(huán)境的配置文件,比如說(shuō):spring.profiles.active=dev,那么在加載完application.properties之后,就會(huì)繼續(xù)加載application-dev.properties文件。之所以會(huì)優(yōu)先加載application.properties是因?yàn)?,在初始化profiles的雙向隊(duì)列時(shí),SpringBoot首先放入了一個(gè)null,而在profile值為null的時(shí)候,是直接加載配置文件名稱拼上后綴的,不進(jìn)行profile拼接的,所以application.properties得到了優(yōu)先加載的機(jī)會(huì);后面再對(duì)配置的spring.profiles.active以及spring.profiles.inclue等文件拼接上profile后再進(jìn)行加載;通過(guò)設(shè)置不同的profile(比如:dev,test.prod等)就可以實(shí)現(xiàn)激活不同環(huán)境了(源碼如下)。當(dāng)我們沒(méi)有指定任何active的profile時(shí),SpringBoot底層會(huì)默認(rèn)的網(wǎng)profiles集合里面加入一個(gè)名為default的profile,這也是為什么當(dāng)我們沒(méi)有指定激活的配置文件的時(shí)候?qū)懸粋€(gè)application-default.properties時(shí)也是會(huì)被加載到的原因。

初始化設(shè)置,加載前的準(zhǔn)備工作

配置文件環(huán)境切換前置.png

準(zhǔn)備好加載的文件全稱,準(zhǔn)備加載:


配置文件環(huán)境切換具體.png

4、我們的配置文件寫(xiě)在哪些路徑下是可以被Spring加載到的呢?

這一點(diǎn)是可以參考 SpringBoot的官方文檔,文檔上寫(xiě)的很詳細(xì)的:

官網(wǎng)中提到的一個(gè)注意點(diǎn):

spring.config.name and spring.config.location are used very early to determine which files have to be loaded. They must be defined as an environment property (typically an OS environment variable, a system property, or a command-line argument)

這兩個(gè)屬性的設(shè)置是要很早設(shè)置的,因?yàn)闉榱四芗虞d到指定的文件,路徑我們必須在要開(kāi)始加載這個(gè)文件之前就指定好啊是吧?所以推薦我們?cè)诓僮飨到y(tǒng)的環(huán)境變量,系統(tǒng)屬性或者啟動(dòng)項(xiàng)目的命令行中使用,方能生效

  1. 首先我們可以通過(guò)配置spring.config.additional-location屬性值來(lái)指定我們需要去加載的路徑,而且這個(gè)配置的路徑是優(yōu)先去加載的
  2. 同樣可以通過(guò)配置spring.config.location這個(gè)屬性值來(lái)指定需要加載的路徑,只不過(guò)這個(gè)配置的優(yōu)先級(jí)是低于spring.config.additional-location的優(yōu)先級(jí)的
  3. 如果我們沒(méi)有配置spring.config.location,Spring則會(huì)加入默認(rèn)的5個(gè)加載路徑:classpath:/,classpath:/config/,file:./,file:./config/* /,file:./config,這也是為什么我們開(kāi)發(fā)的時(shí)候?qū)⑴渲梦募?xiě)在類路徑下就可以被加載到的原因
配置文件的路徑分析.png

5、我們的配置文件被加載后最終是放到了哪里呢?

其實(shí)在我們調(diào)用load方法的時(shí)候,一直傳入了一個(gè)函數(shù)式接口DocumentConsumer(如下代碼),其中這個(gè)addToLoaded(MutablePropertySources::addLast, false)就是函數(shù)式接口的實(shí)現(xiàn),這就是函數(shù)式變成,不得不說(shuō)Spring的開(kāi)發(fā)人員還是膩害的。

  load(profile, this::getPositiveProfileFilter,addToLoaded(MutablePropertySources::addLast, false));

在我們加載完配置文件并封裝成Document后,調(diào)用了上面DocumentConsumer的accept方法,將Document對(duì)象的PropertySources封裝到了MutablePropertySources的propertySourceList中,并將MutablePropertySources對(duì)象放入了loaded集合中:

配置文件加載后的封裝.png

然后等到所有的配置文件都加載封裝完成后,會(huì)統(tǒng)一將這些封裝后的MutablePropertySources對(duì)象放入到Environment中。也就是說(shuō),最終我們所有的配置文件的信息都是加載到了Environment中的,以后我們要是拿都是在Environment中拿的

將配置信息加入到Environment中1.png
將配置文件中的信息加入到Environmen2.png

寫(xiě)在最后

既然以后我們拿配置文件的信息都是在Environment中拿,那我們就可以探索@Value是如何在Environment的了...還有,這僅僅是SpringBoot情況下的文件加載,當(dāng)引入SpringCloud后,有了bootstrap配置文件,就牽扯到了父子容器了,等有時(shí)間了,我們一起探討

最后編輯于
?著作權(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)容