溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

源碼解讀 Spring Boot Profiles

發(fā)布時(shí)間:2020-06-27 10:50:46 來源:網(wǎng)絡(luò) 閱讀:295 作者:颯拉阿依醬 欄目:編程語(yǔ)言

正文

首先,我們先來看下一個(gè)簡(jiǎn)單的 Spring Boot 示例程序,
源碼解讀 Spring Boot Profiles
在主程序方法中,打印容器中獲取到 User 對(duì)象,它只有一個(gè) name 屬性。
源碼解讀 Spring Boot Profiles
這里 name 屬性引用了外部配置 user.username 的值,它是從配置文件中讀取,這里我定義兩個(gè)配置文件設(shè)置該屬性,application.properties 和 application-prod.properties。
源碼解讀 Spring Boot Profiles
有了配置文件之后,啟動(dòng) SimapleSpringApplication 程序,我們首先可以看到日志輸入:User Bean: User(name=one),由此可以看出程序讀取了 application.properties 的 user.username 配置。現(xiàn)在我們?cè)?application.properties 中加入一行:
源碼解讀 Spring Boot Profiles
再次重啟啟動(dòng)程序,可以看到控制臺(tái)如下日志:
源碼解讀 Spring Boot Profiles
此時(shí) User 對(duì)象的name屬性變成了 application-prod.properties 中定義的值,并且日志提示 The following profiles are active: prod 表明了名稱為 prod 的Profile 在程序中激活。接下來我們就從這個(gè)日志入手,探究下這一切是如何發(fā)生的。

首先,根據(jù) IDE 的全局查找功能,直接搜索 The following profiles are active: 這些詞出現(xiàn)的位置,進(jìn)行定位,可以找到這個(gè)日志出現(xiàn)于 SpringApplication#logStartupProfileInfo 方法之中。
源碼解讀 Spring Boot Profiles
源碼解讀 Spring Boot Profiles
從日志方法可以看出打印的 activeProfiles 來自上下文關(guān)聯(lián)的 environment 對(duì)象,再進(jìn)一步查看 logStartupProfileInfo 的調(diào)用位置,可以在 SpringApplication#prepareContext 方法之中找到,這個(gè)方法從命名上就可以看出是主要負(fù)責(zé) Spring Boot 運(yùn)行前容器上下文的預(yù)備工作,
源碼解讀 Spring Boot Profiles
我們重新運(yùn)行程序,通過斷點(diǎn)方式攔截 SpringApplication#prepareContext 方法的指向, 獲取 environment對(duì)象真實(shí)的類型為 StandardEnvironment,是 Environment 接口非Web環(huán)境的標(biāo)準(zhǔn)實(shí)現(xiàn),存儲(chǔ)著一些應(yīng)用配置和 Profiles 信息,如果在Web環(huán)境下,context 關(guān)聯(lián)的就是 StandardServletEnvironment 類型的對(duì)象。
源碼解讀 Spring Boot Profiles
知道了日志打印來自 StandardEnvironment 對(duì)象的 activeProfiles 屬性之后,就需要來看它是在什么時(shí)間被賦值的了。繼續(xù)從調(diào)用鏈的上一級(jí)查找,就到了 SpringApplication#run(java.lang.String...),這也是整個(gè)程序啟動(dòng)的主要方法。

源碼解讀 Spring Boot Profiles
從圖中可以看出第一次獲取到的 environment 對(duì)象來自 SpringApplication#prepareEnvironment 內(nèi)部生成, prepareEnvironment 方法內(nèi)部首先通過 getOrCreateEnvironment 獲取一個(gè)基礎(chǔ)的 ConfigurableEnvironment 實(shí)例,然后對(duì)該實(shí)例對(duì)象初始化配置返回。
源碼解讀 Spring Boot Profiles
正在創(chuàng)建 environment 對(duì)象來自 SpringApplication#getOrCreateEnvironment,看它的實(shí)現(xiàn)就可以驗(yàn)證我們之前提到 environment 對(duì)象類型為 StandardEnvironment。

源碼解讀 Spring Boot Profiles
了解完 environment 的創(chuàng)建,接下來就關(guān)注 environment 的初始化了,這里我們需要關(guān)注 listeners.environmentPrepared(environment) 這行代碼,這里的 listeners 為 SpringApplicationRunListeners 實(shí)例,是監(jiān)聽器 SpringApplicationRunListener 的集合對(duì)象, SpringApplicationRunListener#environmentPrepared 方法中就是對(duì)每個(gè) SpringApplicationRunListener 對(duì)象遍歷指向類似的 environmentPrepared 方法,當(dāng)前集合中只有一個(gè) EventPublishingRunListener 實(shí)例,查看其 environmentPrepared 方法就可以看到它主要就是用于發(fā)布包含 environment 實(shí)例的 ApplicationEnvironmentPreparedEvent 事件,讓其他所有監(jiān)聽該事件的監(jiān)聽器進(jìn)行 environment 實(shí)例的配置。
源碼解讀 Spring Boot Profiles
事件對(duì)象 ApplicationEnvironmentPreparedEvent 還有一個(gè) getEnvironment 方法獲取所傳遞的 environment實(shí)例,我們可以通過看這個(gè)方法被使用的地方,獲取有哪些類在配置 environment 對(duì)象。

源碼解讀 Spring Boot Profiles
經(jīng)過多次的查看,從上圖可以定位到 ConfigFileApplicationListener 類內(nèi)的方法對(duì) environment 對(duì)象進(jìn)行擴(kuò)展,從命名可以看出這個(gè)監(jiān)聽器跟配置文件相關(guān),比如它的一些常量屬性:CONFIG_NAME_PROPERTY,CONFIG_LOCATION_PROPERTY等。從類的注釋可以看出,Spring Boot 程序啟動(dòng)所加載的 application.properties 或 application.yml 默認(rèn)從四個(gè)路徑下加載,我們最常用的就是最后一種,它也可以告訴我們還可以把配置文件放在哪,如何自定義加載配置文件的路徑。

file:./config/:
file:./
classpath:config/
classpath:
源碼解讀 Spring Boot Profiles
將程序斷點(diǎn)設(shè)置于 ConfigFileApplicationListener#onApplicationEvent 方法之內(nèi),重新運(yùn)行程序就看到程序此時(shí)運(yùn)行到了 ConfigFileApplicationListener 類之中,內(nèi)部經(jīng)過多個(gè)方法調(diào)用從 onApplicationEvent 來到了 addPropertySources 方法,這個(gè)方法就是配置文件的屬性源加載到 environment 環(huán)境去的。
源碼解讀 Spring Boot Profiles
這里的 Loader 是 ConfigFileApplicationListener類內(nèi)部私有類,用于協(xié)調(diào)屬性源和配置 Profiles,我們?cè)龠M(jìn)一步跟蹤到它的 load 方法。
源碼解讀 Spring Boot Profiles
我們主要看這個(gè)方法中的是三個(gè)方法:

Loader#initializeProfiles
Loader#addProfileToEnvironment
Loader#load(Profile, DocumentFilterFactory, DocumentConsumer)
第一個(gè)方法 initializeProfiles 初始化 Profiles,給 profiles 屬性添加兩個(gè)元素,null 和 默認(rèn)的Profile。

第二個(gè)方法 addProfileToEnvironment 就是將 Profile 添加到 environment 對(duì)象的 activeProfiles 里,也就是最開始日志打印的 activeProfiles。

第三個(gè)方法就是加載配置文件的數(shù)據(jù)源和 Profies 相關(guān)的屬性。

進(jìn)入 load 方法,這個(gè)方法內(nèi)部通過不同配置路徑去嘗試執(zhí)行另一個(gè) load 方法加載配置文件,這里 name 就是配所要搜索的配置文件名稱,默認(rèn)為 application。
源碼解讀 Spring Boot Profiles
由于我們的配置文件在 ClassPath 下,所以只要留意當(dāng) location 為 classpath:/ 的程序執(zhí)行情況即可。
源碼解讀 Spring Boot Profiles
由于SpringBoot 配置文件支持xml,properties, yml 格式,就需要不同 PropertySourceLoader 支持其文件內(nèi)容的加載:PropertiesPropertySourceLoader 支持 xml,properties 文件,YamlPropertySourceLoader 支持 yml 文件,加載以 .yml 或 .yaml 后綴的文件,Loader#loadForFileExtension 方法就完成了對(duì)這些配置文件的加載。

我們示例程序只有 properties 文件,所以只需要關(guān)注當(dāng) loader 為 PropertiesPropertySourceLoader時(shí)的 Loader#loadForFileExtension 方法的執(zhí)行情況。
源碼解讀 Spring Boot Profiles
loadForFileExtension 內(nèi)部調(diào)用另外一個(gè)加載配置文件的 load 方法,當(dāng)讀取到ClassPath下的application.properties 時(shí),會(huì)執(zhí)行到 Loader#loadDocuments 方法,這個(gè)方法就是把配置文件作為文檔進(jìn)行加載,所有鍵值對(duì)配置都會(huì)以存在 PropertySource 之中,存儲(chǔ)到 Document 對(duì)象中。

!](ww3.sinaimg.cn/large/006tN…)

并且 documents 對(duì)象經(jīng)過 Loader#asDocuments 方法關(guān)聯(lián)上 spring.profiles.active 屬性,profiles 屬性添加一個(gè)定義為 prod 的 Profile,為后面的 Environment 對(duì)象添加 Profile 做準(zhǔn)備,到這里默認(rèn)的配置文件 application.properties 加載完畢了,方法又回到了 Loader#load() 上。
源碼解讀 Spring Boot Profiles
有了新添加的 Profile,繼續(xù)進(jìn)入循環(huán),就會(huì)通過 Loader#addProfileToEnvironment 方法,為 environment 對(duì)象保存激活的 Profile,并且按照之前的邏輯,讀取名為 application-prod.properties 的配置文件,命名方式可以從之前的 Loader#loadForFileExtension 的第462行就可以看出:
源碼解讀 Spring Boot Profiles
在 Loader#load() 方法讀取了所有配置文件后,執(zhí)行 Loader#addLoadedPropertySources,將對(duì)應(yīng)屬性源 PropertySource 存儲(chǔ)到 environment 對(duì)象中,并且 application-prod.properties 順序先于默認(rèn)配置文件,就是為了后面程序應(yīng)用相同名稱配置的時(shí)候,優(yōu)先采用元素位置在前的配置。
源碼解讀 Spring Boot Profiles
至此,所有配置文件上的數(shù)據(jù)加載完存儲(chǔ)到了與當(dāng)前上下文關(guān)聯(lián)的 environment 對(duì)象中,將 prod 作為 Active Profile 激活特定環(huán)境配置的工作就完成了。

小結(jié)

雖然只是探究 Spring Boot 程序如何加載和應(yīng)用 Profile,但通過這次源碼分析,我們可以發(fā)現(xiàn) SpringBoot 雖簡(jiǎn)單易用,但是內(nèi)部實(shí)現(xiàn)邏輯設(shè)計(jì)是比較復(fù)雜的,無(wú)論是資源的加載,數(shù)據(jù)的解析都有專門的組件類去處理,大量使用事件通知和設(shè)計(jì)模式,在分析源碼時(shí)少不了一次又一次的運(yùn)行斷點(diǎn),不過這需要我們充分利用DE工具調(diào)試功能,在錯(cuò)綜復(fù)雜的代碼中能更準(zhǔn)確地定位目標(biāo)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI