正文
我們擁有十載網(wǎng)頁設(shè)計和網(wǎng)站建設(shè)經(jīng)驗,從網(wǎng)站策劃到網(wǎng)站制作,我們的網(wǎng)頁設(shè)計師為您提供的解決方案。為企業(yè)提供網(wǎng)站設(shè)計、成都網(wǎng)站設(shè)計、微信開發(fā)、微信小程序開發(fā)、成都手機網(wǎng)站制作、H5響應式網(wǎng)站、等業(yè)務。無論您有什么樣的網(wǎng)站設(shè)計或者設(shè)計方案要求,我們都將富于創(chuàng)造性的提供專業(yè)設(shè)計服務并滿足您的需求。
首先,我們先來看下一個簡單的 Spring Boot 示例程序,
在主程序方法中,打印容器中獲取到 User 對象,它只有一個 name 屬性。
這里 name 屬性引用了外部配置 user.username 的值,它是從配置文件中讀取,這里我定義兩個配置文件設(shè)置該屬性,application.properties 和 application-prod.properties。
有了配置文件之后,啟動 SimapleSpringApplication 程序,我們首先可以看到日志輸入:User Bean: User(name=one),由此可以看出程序讀取了 application.properties 的 user.username 配置。現(xiàn)在我們在 application.properties 中加入一行:
再次重啟啟動程序,可以看到控制臺如下日志:
此時 User 對象的name屬性變成了 application-prod.properties 中定義的值,并且日志提示 The following profiles are active: prod 表明了名稱為 prod 的Profile 在程序中激活。接下來我們就從這個日志入手,探究下這一切是如何發(fā)生的。
首先,根據(jù) IDE 的全局查找功能,直接搜索 The following profiles are active: 這些詞出現(xiàn)的位置,進行定位,可以找到這個日志出現(xiàn)于 SpringApplication#logStartupProfileInfo 方法之中。
從日志方法可以看出打印的 activeProfiles 來自上下文關(guān)聯(lián)的 environment 對象,再進一步查看 logStartupProfileInfo 的調(diào)用位置,可以在 SpringApplication#prepareContext 方法之中找到,這個方法從命名上就可以看出是主要負責 Spring Boot 運行前容器上下文的預備工作,
我們重新運行程序,通過斷點方式攔截 SpringApplication#prepareContext 方法的指向, 獲取 environment對象真實的類型為 StandardEnvironment,是 Environment 接口非Web環(huán)境的標準實現(xiàn),存儲著一些應用配置和 Profiles 信息,如果在Web環(huán)境下,context 關(guān)聯(lián)的就是 StandardServletEnvironment 類型的對象。
知道了日志打印來自 StandardEnvironment 對象的 activeProfiles 屬性之后,就需要來看它是在什么時間被賦值的了。繼續(xù)從調(diào)用鏈的上一級查找,就到了 SpringApplication#run(java.lang.String...),這也是整個程序啟動的主要方法。
從圖中可以看出第一次獲取到的 environment 對象來自 SpringApplication#prepareEnvironment 內(nèi)部生成, prepareEnvironment 方法內(nèi)部首先通過 getOrCreateEnvironment 獲取一個基礎(chǔ)的 ConfigurableEnvironment 實例,然后對該實例對象初始化配置返回。
正在創(chuàng)建 environment 對象來自 SpringApplication#getOrCreateEnvironment,看它的實現(xiàn)就可以驗證我們之前提到 environment 對象類型為 StandardEnvironment。
了解完 environment 的創(chuàng)建,接下來就關(guān)注 environment 的初始化了,這里我們需要關(guān)注 listeners.environmentPrepared(environment) 這行代碼,這里的 listeners 為 SpringApplicationRunListeners 實例,是監(jiān)聽器 SpringApplicationRunListener 的集合對象, SpringApplicationRunListener#environmentPrepared 方法中就是對每個 SpringApplicationRunListener 對象遍歷指向類似的 environmentPrepared 方法,當前集合中只有一個 EventPublishingRunListener 實例,查看其 environmentPrepared 方法就可以看到它主要就是用于發(fā)布包含 environment 實例的 ApplicationEnvironmentPreparedEvent 事件,讓其他所有監(jiān)聽該事件的監(jiān)聽器進行 environment 實例的配置。
事件對象 ApplicationEnvironmentPreparedEvent 還有一個 getEnvironment 方法獲取所傳遞的 environment實例,我們可以通過看這個方法被使用的地方,獲取有哪些類在配置 environment 對象。
經(jīng)過多次的查看,從上圖可以定位到 ConfigFileApplicationListener 類內(nèi)的方法對 environment 對象進行擴展,從命名可以看出這個監(jiān)聽器跟配置文件相關(guān),比如它的一些常量屬性:CONFIG_NAME_PROPERTY,CONFIG_LOCATION_PROPERTY等。從類的注釋可以看出,Spring Boot 程序啟動所加載的 application.properties 或 application.yml 默認從四個路徑下加載,我們最常用的就是最后一種,它也可以告訴我們還可以把配置文件放在哪,如何自定義加載配置文件的路徑。
file:./config/:
file:./
classpath:config/
classpath:
將程序斷點設(shè)置于 ConfigFileApplicationListener#onApplicationEvent 方法之內(nèi),重新運行程序就看到程序此時運行到了 ConfigFileApplicationListener 類之中,內(nèi)部經(jīng)過多個方法調(diào)用從 onApplicationEvent 來到了 addPropertySources 方法,這個方法就是配置文件的屬性源加載到 environment 環(huán)境去的。
這里的 Loader 是 ConfigFileApplicationListener類內(nèi)部私有類,用于協(xié)調(diào)屬性源和配置 Profiles,我們再進一步跟蹤到它的 load 方法。
我們主要看這個方法中的是三個方法:
Loader#initializeProfiles
Loader#addProfileToEnvironment
Loader#load(Profile, DocumentFilterFactory, DocumentConsumer)
第一個方法 initializeProfiles 初始化 Profiles,給 profiles 屬性添加兩個元素,null 和 默認的Profile。
第二個方法 addProfileToEnvironment 就是將 Profile 添加到 environment 對象的 activeProfiles 里,也就是最開始日志打印的 activeProfiles。
第三個方法就是加載配置文件的數(shù)據(jù)源和 Profies 相關(guān)的屬性。
進入 load 方法,這個方法內(nèi)部通過不同配置路徑去嘗試執(zhí)行另一個 load 方法加載配置文件,這里 name 就是配所要搜索的配置文件名稱,默認為 application。
由于我們的配置文件在 ClassPath 下,所以只要留意當 location 為 classpath:/ 的程序執(zhí)行情況即可。
由于SpringBoot 配置文件支持xml,properties, yml 格式,就需要不同 PropertySourceLoader 支持其文件內(nèi)容的加載:PropertiesPropertySourceLoader 支持 xml,properties 文件,YamlPropertySourceLoader 支持 yml 文件,加載以 .yml 或 .yaml 后綴的文件,Loader#loadForFileExtension 方法就完成了對這些配置文件的加載。
我們示例程序只有 properties 文件,所以只需要關(guān)注當 loader 為 PropertiesPropertySourceLoader時的 Loader#loadForFileExtension 方法的執(zhí)行情況。
loadForFileExtension 內(nèi)部調(diào)用另外一個加載配置文件的 load 方法,當讀取到ClassPath下的application.properties 時,會執(zhí)行到 Loader#loadDocuments 方法,這個方法就是把配置文件作為文檔進行加載,所有鍵值對配置都會以存在 PropertySource 之中,存儲到 Document 對象中。
!](ww3.sinaimg.cn/large/006tN…)
并且 documents 對象經(jīng)過 Loader#asDocuments 方法關(guān)聯(lián)上 spring.profiles.active 屬性,profiles 屬性添加一個定義為 prod 的 Profile,為后面的 Environment 對象添加 Profile 做準備,到這里默認的配置文件 application.properties 加載完畢了,方法又回到了 Loader#load() 上。
有了新添加的 Profile,繼續(xù)進入循環(huán),就會通過 Loader#addProfileToEnvironment 方法,為 environment 對象保存激活的 Profile,并且按照之前的邏輯,讀取名為 application-prod.properties 的配置文件,命名方式可以從之前的 Loader#loadForFileExtension 的第462行就可以看出:
在 Loader#load() 方法讀取了所有配置文件后,執(zhí)行 Loader#addLoadedPropertySources,將對應屬性源 PropertySource 存儲到 environment 對象中,并且 application-prod.properties 順序先于默認配置文件,就是為了后面程序應用相同名稱配置的時候,優(yōu)先采用元素位置在前的配置。
至此,所有配置文件上的數(shù)據(jù)加載完存儲到了與當前上下文關(guān)聯(lián)的 environment 對象中,將 prod 作為 Active Profile 激活特定環(huán)境配置的工作就完成了。
小結(jié)
雖然只是探究 Spring Boot 程序如何加載和應用 Profile,但通過這次源碼分析,我們可以發(fā)現(xiàn) SpringBoot 雖簡單易用,但是內(nèi)部實現(xiàn)邏輯設(shè)計是比較復雜的,無論是資源的加載,數(shù)據(jù)的解析都有專門的組件類去處理,大量使用事件通知和設(shè)計模式,在分析源碼時少不了一次又一次的運行斷點,不過這需要我們充分利用DE工具調(diào)試功能,在錯綜復雜的代碼中能更準確地定位目標。