您好,登錄后才能下訂單哦!
正文
首先,我們先來看下一個(gè)簡(jiǎn)單的 Spring Boot 示例程序,
在主程序方法中,打印容器中獲取到 User 對(duì)象,它只有一個(gè) name 屬性。
這里 name 屬性引用了外部配置 user.username 的值,它是從配置文件中讀取,這里我定義兩個(gè)配置文件設(shè)置該屬性,application.properties 和 application-prod.properties。
有了配置文件之后,啟動(dòng) SimapleSpringApplication 程序,我們首先可以看到日志輸入:User Bean: User(name=one),由此可以看出程序讀取了 application.properties 的 user.username 配置。現(xiàn)在我們?cè)?application.properties 中加入一行:
再次重啟啟動(dòng)程序,可以看到控制臺(tái)如下日志:
此時(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 方法之中。
從日志方法可以看出打印的 activeProfiles 來自上下文關(guān)聯(lián)的 environment 對(duì)象,再進(jìn)一步查看 logStartupProfileInfo 的調(diào)用位置,可以在 SpringApplication#prepareContext 方法之中找到,這個(gè)方法從命名上就可以看出是主要負(fù)責(zé) Spring Boot 運(yùn)行前容器上下文的預(yù)備工作,
我們重新運(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ì)象。
知道了日志打印來自 StandardEnvironment 對(duì)象的 activeProfiles 屬性之后,就需要來看它是在什么時(shí)間被賦值的了。繼續(xù)從調(diào)用鏈的上一級(jí)查找,就到了 SpringApplication#run(java.lang.String...),這也是整個(gè)程序啟動(dòng)的主要方法。
從圖中可以看出第一次獲取到的 environment 對(duì)象來自 SpringApplication#prepareEnvironment 內(nèi)部生成, prepareEnvironment 方法內(nèi)部首先通過 getOrCreateEnvironment 獲取一個(gè)基礎(chǔ)的 ConfigurableEnvironment 實(shí)例,然后對(duì)該實(shí)例對(duì)象初始化配置返回。
正在創(chuàng)建 environment 對(duì)象來自 SpringApplication#getOrCreateEnvironment,看它的實(shí)現(xiàn)就可以驗(yàn)證我們之前提到 environment 對(duì)象類型為 StandardEnvironment。
了解完 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í)例的配置。
事件對(duì)象 ApplicationEnvironmentPreparedEvent 還有一個(gè) getEnvironment 方法獲取所傳遞的 environment實(shí)例,我們可以通過看這個(gè)方法被使用的地方,獲取有哪些類在配置 environment 對(duì)象。
經(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:
將程序斷點(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)境去的。
這里的 Loader 是 ConfigFileApplicationListener類內(nèi)部私有類,用于協(xié)調(diào)屬性源和配置 Profiles,我們?cè)龠M(jìn)一步跟蹤到它的 load 方法。
我們主要看這個(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。
由于我們的配置文件在 ClassPath 下,所以只要留意當(dāng) location 為 classpath:/ 的程序執(zhí)行情況即可。
由于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í)行情況。
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() 上。
有了新添加的 Profile,繼續(xù)進(jìn)入循環(huán),就會(huì)通過 Loader#addProfileToEnvironment 方法,為 environment 對(duì)象保存激活的 Profile,并且按照之前的邏輯,讀取名為 application-prod.properties 的配置文件,命名方式可以從之前的 Loader#loadForFileExtension 的第462行就可以看出:
在 Loader#load() 方法讀取了所有配置文件后,執(zhí)行 Loader#addLoadedPropertySources,將對(duì)應(yīng)屬性源 PropertySource 存儲(chǔ)到 environment 對(duì)象中,并且 application-prod.properties 順序先于默認(rèn)配置文件,就是為了后面程序應(yīng)用相同名稱配置的時(shí)候,優(yōu)先采用元素位置在前的配置。
至此,所有配置文件上的數(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)。
免責(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)容。