前言
關(guān)于nacos客戶端如何獲取到服務(wù)端的配置信息的主流程源碼分析和客戶端拉取服務(wù)端變更的主流程源碼分析在前兩篇文章都分析過了,雖然讀的人并不是很多,加起來也沒有200個人閱讀,也不知道是我寫的不好,還是大家對nacos的源碼并不感興趣,不過既然是系列教程,我們還是要堅持把這個小系列教程做完,本小節(jié)本來要介紹nacos和spring boot整合的主流程源碼分析的,但是思來想去,還是先花幾個小節(jié)把spring boot的啟動流程中涉及到的主要組件一起學(xué)習(xí)一下,這樣可能分析nacos和spring boot整合時候,大家理解的可能會快一點(diǎn),達(dá)到事半功倍的效果
本文為原創(chuàng)文章,主要講解spring boot一個核心組件EnvironmentPostProcessor,閱讀本文大約7分鐘,如果覺得本文寫的不錯,請給一個點(diǎn)贊或者關(guān)注一下,您的支持是我寫作的最大動力
EnvironmentPostProcessor功能說明
EnvironmentPostProcessor從名字上看,叫做"環(huán)境后置處理器",它是一個接口,它可以再spring上下文啟動的時候,去初始化一些基本配置信息,將某些變量信息,加載到spring容器上下文中,更加通俗的理解就是它可以用來解析加載我們自定義額外properties
舉例來說:
1) 我們可以使用EnvironmentPostProcessor來加載json文件中的kv屬性,將其解析到全局的環(huán)境變量里面,然后使用@Value來獲取到信息
2)我們甚至可以使用EnvironmentPostProcessor來加載遠(yuǎn)程的配置,例如我們可以使用這個組件加載nacos的服務(wù)器的配置信息
EnvironmentPostProcessor 實戰(zhàn)——加載本地json數(shù)據(jù)
step1.編寫測試數(shù)據(jù)
1.1.首先在spring boot的hello world項目的resource文件夾下新建一個測試的json——custom_properties文件,就寫兩個簡單的kv值
1.2.存在的位置在resources/json/custom_properties.json,格式如下
step2.自定義EnvironmentPostProcessor
2.1.新建BazingaJsonEnvironmentPostProcessor,這個類主要是讀取我們剛才寫的cutom_properties.json的文本信息,然后加載到spring的Environment中,BazingaJsonEnvironmentPostProcessor繼承我們今天的主角EnvironmentPostProcessor,這是一個接口,我們需要實現(xiàn)postProcessorEnvironment接口
這個接口就是首先讀取cutom_properties.json轉(zhuǎn)換成流,然后將流讀取成kv值,變成properties,最后寫入到ConfigurableEnvironment中去,這樣當(dāng)spring的context開始初始化的時候,就能夠在ConfigurableEnvironment中讀取到變量,主要實現(xiàn)代碼如下
核心實現(xiàn)邏輯
2.2 我們有了自定義的BazingaJsonEnvironmentPostProcessor之后,還是不夠的,還缺一個驅(qū)動力,springboot啟動的時候,不會去主動讀取我們自定義的類的,所以我們還需要利用spring boot的SPI機(jī)制,來加載我們的環(huán)境后置處理器,我們再resources下新建一個文件夾META-INF,然后在META-INF下新建文件spring.factories,整體結(jié)構(gòu)如下圖所示
spring.factories
2.3 文件的內(nèi)容也是有規(guī)范的,我們要指定spring的接口具體的實現(xiàn)類是我們自定義的
到這邊為止,我們所有的配置就到此結(jié)束了,最后我們寫一個簡單的測試controller,來測試一下我們自定義的兩個KV能否在@Value這種注解中生效
我們整體的項目骨架圖如下圖所示
我們簡單地運(yùn)行一下localhost:8080/json/hello,可以在瀏覽器中成功展示我們的"您好,世界",這樣我們就可以成功地將我們自定義的文件中的屬性值,讓spring boot加載到,并成功運(yùn)用到我們真實的項目中去了,雖然很簡單,但是這個組件EnvironmentPostProcessor的使用套路,大家還是要掌握的,很多優(yōu)秀的源代碼中都運(yùn)用了這個套路,例如攜程的Apollo.
EnvironmentPostProcessor的原理講解
我們已經(jīng)初步懂得了EnvironmentPostProcessor的使用技巧,還有它的作用,在講解原理之前,我們首先要記得一點(diǎn),如果你有特殊的配置文件要加載的話,使用EnvironmentPostProcessor是很方便的,因為spring boot加載它的順序是所有組件中最靠前的,等下我們分析看源碼的時候,就能體會到這點(diǎn)了
這個源碼分析也很簡單,debug走一遍源碼5分鐘也就到了我們今天說的這個組件了,入口當(dāng)然是我們的main函數(shù)了,我們只要看SpringApplication的源碼基本上就了解了
我們先打開SpringApplication#run方法
step1:我們第一個要關(guān)注的就是spring boot首先初始化了一個全局的事件監(jiān)聽器,這個事件監(jiān)聽器會伴隨著springboot的整個生命周期,這個我們以后也會多次接觸這個組件
初始化全局的事件監(jiān)聽器EventPublishingRunListener
step2:接下來就是開始準(zhǔn)備springboot所有配置文件存儲的倉庫Environment,這個其實也很好理解,spring是管理bean的,bean里面也有很多屬性,所以優(yōu)先收集整個上下文的配置屬性信息,將其放在一個籃子(Environment)里面,然后以后想要什么,就從籃子里面去獲取,而不是在想要的時候,在去想辦法獲取,這樣會導(dǎo)致整個springboot的代碼變得不是很優(yōu)雅
step3:我們進(jìn)入prepareEnvironment方法
我們可以看到首先先調(diào)用getOrCreateEnvironment創(chuàng)建好一個"籃子",有了這個"籃子"我們才可以在這個"籃子"里面放我們想要的東西,所以我們可以接著看下一行的#configureEnvironment方法,例如System.setProperties之類信息會在這邊進(jìn)行加載,放到籃子里面
主要是加載如下的配置文件,你不要小看這樣的配置文件,運(yùn)維很有用,運(yùn)維啟動啟動一般會配置啟動參數(shù),其實也就是在這個地方,啟動參數(shù)被會spring boot解析到作為默認(rèn)選項,加載到上下文中,作為啟動的核心參數(shù)啟動,但是一般這些參數(shù)叫做默認(rèn)參數(shù),優(yōu)先級是最低的,如果你在代碼有同key值的時候,就會覆蓋運(yùn)維配置的系統(tǒng)級變量值,這點(diǎn)我們平時開發(fā)的時候要注意,如下圖所示,這點(diǎn)很重要,不過不是我們今天的重點(diǎn)
其實就算大家不看spring的源碼,但是也知道spring有一個很牛的源碼原則就是"開閉原則","籃子"是spring自己造的,東西也是spring自己放的,不能讓我們這些使用者沒有一點(diǎn)機(jī)會去做點(diǎn)什么,肯定不是springboot的風(fēng)格,所以spring就開了一個口子,簡單地說就是開了一個后門,貌似在說:我東西放完了,你有什么東西要放的啊,口子就在如下的代碼中
我們可以看到方法名叫做environmentPrepared,調(diào)用者是listener,是一個監(jiān)聽器,也就是我們上文說的EventPublishingRunListener監(jiān)聽器,就是像是在說:"同志們,籃子我已經(jīng)弄好了,你們有啥東西想放的趕緊放,放完我準(zhǔn)備開始用了"
最后我們跟著源碼debug,會到了ConfigFileApplicationListener的監(jiān)聽器實例,它監(jiān)聽的是一個ApplicationEnvironmentPreparedEvent,故其名曰"應(yīng)用環(huán)境準(zhǔn)備好事件",雖然有點(diǎn)繞口,也不通順,但是看到這邊我們就動了,springboot是靠一種事件訂閱的方式來做解耦合的,源碼如下
ConfigFileApplicationListener.JAVA
debug到現(xiàn)在,我們進(jìn)入onApplicationEnvironmentPreparedEvent這個方法,就可以看到我們今天的主角的影子了,有請我們的主角登場
EnvironmentPostProcessor初次出現(xiàn)
我們可以看到當(dāng)loadPostProcessors執(zhí)行完之后,看方法名我們也是是加載當(dāng)前項目中EnvironmentPostProcessor,然后排序,最后調(diào)用我們剛剛說的postProcessorEnvironment方法,這個方法的具體實現(xiàn),也就是我們上文實戰(zhàn)小節(jié)的那個BazingaJSONEnvironmentPostProcessor的具體實踐了,到現(xiàn)在為止,我們已經(jīng)跟蹤源碼知道整個加載的主流程了
現(xiàn)在還剩下最后一個問題,為啥我們要寫META-INF的spring.factorie配置文件了,答案就是在loadPostProcessors中了,這個方法是一個靜態(tài)方法,調(diào)用SpringFactoriesLoader的loadFactories方法
這個SpringFactoriesLoader就有一個類似的SPI機(jī)制,會去加載META-INF下定義的類,因為這邊源碼比較長,但是并不是很難理解,我就貼下一個全局的類加載路徑,大家基本上看一下也就能懂~
SPI的類加載路徑
好了,到此為止,整個分析也就結(jié)束了,基本上還是很簡單的
小結(jié)
本小節(jié)其實是為了簡介nacos整個spring boot源碼講解的一個鋪墊,但是單獨(dú)使用一個小節(jié),也是合理的,EnvironmentPostProcessor組件在整個spring boot的生態(tài)中還是比較重要的,如果吧spring boot最后運(yùn)行成功,比如一個機(jī)器人,那么EnvironmentPostProcessor組件就是開始組裝這個機(jī)器人之前存放零件的倉庫,以后缺什么組件都可以在這個容器中尋找~