上文《一文掌握 Spring Boot Profiles》 是對 Spring Boot Profiles 的介紹和使用,所以本文將從源碼角度探究 Spring Boot Profiles,讓咱們看下 Spring Boot 底層是如何應用 Profiles 進行環境配置的隔離與生效的。java
首先,咱們先來看下一個簡單的 Spring Boot 示例程序,spring
在主程序方法中,打印容器中獲取到 User 對象,它只有一個 name
屬性。設計模式
這裏 name
屬性引用了外部配置 user.username
的值,它是從配置文件中讀取,這裏我定義兩個配置文件設置該屬性,application.properties
和 application-prod.properties
。app
有了配置文件以後,啓動 SimapleSpringApplication
程序,咱們首先能夠看到日誌輸入:User Bean: User(name=one)
,由此能夠看出程序讀取了 application.properties
的 user.username
配置。如今咱們在 application.properties
中加入一行:微服務
再次重啓啓動程序,能夠看到控制檯以下日誌:工具
此時 User 對象的name屬性變成了 application-prod.properties
中定義的值,而且日誌提示 The following profiles are active: prod
代表了名稱爲 prod
的Profile 在程序中激活。接下來咱們就從這個日誌入手,探究下這一切是如何發生的。源碼分析
首先,根據 IDE 的全局查找功能,直接搜索 The following profiles are active:
這些詞出現的位置,進行定位,能夠找到這個日誌出現於 SpringApplication#logStartupProfileInfo
方法之中。spa
從日誌方法能夠看出打印的 activeProfiles
來自上下文關聯的 environment
對象,再進一步查看 logStartupProfileInfo
的調用位置,能夠在 SpringApplication#prepareContext
方法之中找到,這個方法從命名上就能夠看出是主要負責 Spring Boot 運行前容器上下文的預備工做,設計
咱們從新運行程序,經過斷點方式攔截 SpringApplication#prepareContext
方法的指向, 獲取 environment
對象真實的類型爲 StandardEnvironment,是 Environment 接口非Web環境的標準實現,存儲着一些應用配置和 Profiles 信息,若是在Web環境下,context 關聯的就是 StandardServletEnvironment 類型的對象。3d
知道了日誌打印來自 StandardEnvironment 對象的 activeProfiles
屬性以後,就須要來看它是在什麼時間被賦值的了。繼續從調用鏈的上一級查找,就到了 SpringApplication#run(java.lang.String...)
,這也是整個程序啓動的主要方法。
從圖中能夠看出第一次獲取到的 environment
對象來自 SpringApplication#prepareEnvironment
內部生成, prepareEnvironment
方法內部首先經過 getOrCreateEnvironment
獲取一個基礎的 ConfigurableEnvironment
實例,而後對該實例對象初始化配置返回。
正在建立 environment
對象來自 SpringApplication#getOrCreateEnvironment
,看它的實現就能夠驗證咱們以前提到 environment
對象類型爲 StandardEnvironment。
瞭解完 environment
的建立,接下來就關注 environment
的初始化了,這裏咱們須要關注 listeners.environmentPrepared(environment)
這行代碼,這裏的 listeners
爲 SpringApplicationRunListeners 實例,是監聽器 SpringApplicationRunListener 的集合對象, SpringApplicationRunListener#environmentPrepared
方法中就是對每一個 SpringApplicationRunListener 對象遍歷指向相似的 environmentPrepared
方法,當前集合中只有一個 EventPublishingRunListener 實例,查看其 environmentPrepared
方法就能夠看到它主要就是用於發佈包含 environment
實例的 ApplicationEnvironmentPreparedEvent
事件,讓其餘全部監聽該事件的監聽器進行 environment
實例的配置。
事件對象 ApplicationEnvironmentPreparedEvent 還有一個 getEnvironment
方法獲取所傳遞的 environment
實例,咱們能夠經過看這個方法被使用的地方,獲取有哪些類在配置 environment
對象。
通過屢次的查看,從上圖能夠定位到 ConfigFileApplicationListener 類內的方法對 environment
對象進行擴展,從命名能夠看出這個監聽器跟配置文件相關,好比它的一些常量屬性:CONFIG_NAME_PROPERTY
,CONFIG_LOCATION_PROPERTY
等。從類的註釋能夠看出,Spring Boot 程序啓動所加載的 application.properties
或 application.yml
默認從四個路徑下加載,咱們最經常使用的就是最後一種,它也能夠告訴咱們還能夠把配置文件放在哪,如何自定義加載配置文件的路徑。
file:./config/:
file:./
classpath:config/
classpath:
將程序斷點設置於 ConfigFileApplicationListener#onApplicationEvent
方法以內,從新運行程序就看到程序此時運行到了 ConfigFileApplicationListener
類之中,內部通過多個方法調用從 onApplicationEvent
來到了 addPropertySources
方法,這個方法就是配置文件的屬性源加載到 environment
環境去的。
這裏的 Loader
是 ConfigFileApplicationListener
類內部私有類,用於協調屬性源和配置 Profiles,咱們再進一步跟蹤到它的 load 方法。
咱們主要看這個方法中的是三個方法:
Loader#initializeProfiles
Loader#addProfileToEnvironment
Loader#load(Profile, DocumentFilterFactory, DocumentConsumer)
第一個方法 initializeProfiles
初始化 Profiles,給 profiles
屬性添加兩個元素,null
和 默認的Profile。
第二個方法 addProfileToEnvironment
就是將 Profile 添加到 environment
對象的 activeProfiles
裏,也就是最開始日誌打印的 activeProfiles
。
第三個方法就是加載配置文件的數據源和 Profies 相關的屬性。
進入 load
方法,這個方法內部經過不一樣配置路徑去嘗試執行另外一個 load
方法加載配置文件,這裏 name
就是配所要搜索的配置文件名稱,默認爲 application
。
因爲咱們的配置文件在 ClassPath 下,因此只要留意當 location
爲 classpath:/
的程序執行狀況便可。
因爲SpringBoot 配置文件支持xml
,properties
, yml
格式,就須要不一樣 PropertySourceLoader 支持其文件內容的加載:PropertiesPropertySourceLoader 支持 xml
,properties
文件,YamlPropertySourceLoader 支持 yml 文件,加載以 .yml
或 .yaml
後綴的文件,Loader#loadForFileExtension
方法就完成了對這些配置文件的加載。
咱們示例程序只有 properties
文件,因此只須要關注當 loader
爲 PropertiesPropertySourceLoader時的 Loader#loadForFileExtension
方法的執行狀況。
loadForFileExtension
內部調用另一個加載配置文件的 load
方法,當讀取到ClassPath下的application.properties
時,會執行到 Loader#loadDocuments
方法,這個方法就是把配置文件做爲文檔進行加載,全部鍵值對配置都會以存在 PropertySource 之中,存儲到 Document 對象中。
!](ww3.sinaimg.cn/large/006tN…)
而且 documents
對象通過 Loader#asDocuments
方法關聯上 spring.profiles.active
屬性,profiles
屬性添加一個定義爲 prod
的 Profile,爲後面的 Environment 對象添加 Profile 作準備,到這裏默認的配置文件 application.properties
加載完畢了,方法又回到了 Loader#load()
上。
有了新添加的 Profile,繼續進入循環,就會經過 Loader#addProfileToEnvironment
方法,爲 environment
對象保存激活的 Profile,而且按照以前的邏輯,讀取名爲 application-prod.properties
的配置文件,命名方式能夠從以前的 Loader#loadForFileExtension
的第462行就能夠看出:
在 Loader#load()
方法讀取了全部配置文件後,執行 Loader#addLoadedPropertySources
,將對應屬性源 PropertySource 存儲到 environment
對象中,而且 application-prod.properties
順序先於默認配置文件,就是爲了後面程序應用相同名稱配置的時候,優先採用元素位置在前的配置。
至此,全部配置文件上的數據加載完存儲到了與當前上下文關聯的 environment
對象中,將 prod
做爲 Active Profile 激活特定環境配置的工做就完成了。
雖然只是探究 Spring Boot 程序如何加載和應用 Profile,但經過此次源碼分析,咱們能夠發現 SpringBoot 雖簡單易用,可是內部實現邏輯設計是比較複雜的,不管是資源的加載,數據的解析都有專門的組件類去處理,大量使用事件通知和設計模式,在分析源碼時少不了一次又一次的運行斷點,不過這須要咱們充分利用DE工具調試功能,在錯綜複雜的代碼中能更準確地定位目標。