不知你是否跟我一樣蚓耽,在剛開(kāi)始使用SpringCloud配置中心的時(shí)候也有很多的疑惑:
- SpringCloud是什么時(shí)候去拉取配置中心的误辑?
- 配置中心客戶(hù)端的配置信息為什么要寫(xiě)在bootstrap文件中?
- 對(duì)象中注入的屬性是如何動(dòng)態(tài)刷新的尤泽?
- 一些開(kāi)源的配置中心是如何整合SpringCloud的欣簇?
本文就通過(guò)探討上述問(wèn)題來(lái)探秘SpringCloud配置中心核心的底層原理规脸。
從SpringBoot的啟動(dòng)過(guò)程說(shuō)起
在SpringBoot啟動(dòng)的時(shí)候會(huì)經(jīng)歷一系列步驟,核心就是SpringApplication的run方法的邏輯
整個(gè)過(guò)程大致可以劃分為三個(gè)階段:
ApplicationContext刷新前階段醉蚁,這個(gè)階段主要也干三件事
- 準(zhǔn)備Environment(注意我這里加粗了燃辖,你懂得),也就是準(zhǔn)備SpringBoot的整個(gè)外部化配置的對(duì)象
- 創(chuàng)建一個(gè)ApplicationContext
- 為ApplicationContext做一些準(zhǔn)備工作
準(zhǔn)備Environment网棍,也就是準(zhǔn)備外部化配置
只需要在這個(gè)階段加載配置中心的配置黔龟,放到Environment中,后面在整個(gè)ApplicationContext刷新階段創(chuàng)建Bean的時(shí)候滥玷,就可以使用到配置中心的配置了
準(zhǔn)備Environment的核心操作
準(zhǔn)備環(huán)境的核心操作有一部分是SpringBoot的內(nèi)容氏身,詳情請(qǐng)看SpringBoot啟動(dòng)流程之環(huán)境準(zhǔn)備
,大致概括為在已經(jīng)準(zhǔn)好環(huán)境的時(shí)候惑畴,會(huì)廣播一個(gè)環(huán)境已經(jīng)準(zhǔn)備的事件蛋欣,然后監(jiān)聽(tīng)器ConfigFileApplicationListener
會(huì)加載我們的本地配置文件,在SpringCloud中如贷,還有一個(gè)額外的監(jiān)聽(tīng)器陷虎,用于遠(yuǎn)程配置獲取。一共有兩個(gè)監(jiān)聽(tīng)器
ConfigFileApplicationListener
BootstrapApplicationListener
這些監(jiān)聽(tīng)器都是通過(guò)前置操作從spring.factories
配置文件中加載的
ConfigFileApplicationListener
杠袱,用來(lái)處理配置文件的尚猿,他會(huì)解析配置文件的配置,放到Environment
中
BootstrapApplicationListener
這個(gè)跟本文探討的主題相關(guān)了楣富,它是用來(lái)專(zhuān)門(mén)來(lái)跟配置中心交互的
到這凿掂,我們就找到了SpringCloud
配置中心配置拉取的整個(gè)入口邏輯
不過(guò)在分析BootstrapApplicationListener
是如何從配置中心拉取配置的之前,先來(lái)張圖總結(jié)一下這部分prepareEnvironment
的操作
SpringCloud巧妙地拉取配置
在BootstrapApplicationListener中纹蝴,他首先也會(huì)創(chuàng)建一個(gè)SpringApplication去執(zhí)行
其實(shí)本質(zhì)上就是創(chuàng)建一個(gè)Spring
容器庄萎,也就是ApplicationContext
這個(gè)容器非常重要,這個(gè)容器是專(zhuān)門(mén)用來(lái)跟配置中心交互的
這個(gè)容器在創(chuàng)建的時(shí)候會(huì)給它兩個(gè)比較重要的配置
第一個(gè)就是設(shè)置這個(gè)容器所用的配置文件的名稱(chēng)
默認(rèn)就是bootstrap
這就解釋了為什么配置中心的配置信息需要寫(xiě)在bootstrap
配置文件中
第二個(gè)就是會(huì)加入一個(gè)配置類(lèi)
BootstrapImportSelectorConfiguration
這個(gè)配置類(lèi)又會(huì)通過(guò)
@Import
注解導(dǎo)入另一個(gè)配置類(lèi)BootstrapImportSelector
`BootstrapImportSelector
實(shí)現(xiàn)了(間接)ImportSelector
接口
那么這個(gè)容器在啟動(dòng)的時(shí)候塘安,就會(huì)調(diào)用BootstrapImportSelector
的selectImports
方法的實(shí)現(xiàn)獲取到一些配置類(lèi)
而BootstrapImportSelector
的selectImports
實(shí)現(xiàn)從截圖中也就可以看出
他會(huì)加載所有的spring.factories
中的鍵為org.springframework.cloud.bootstrap.BootstrapConfiguration
的配置類(lèi)
其實(shí)這里@BootstrapConfiguration的作用其實(shí)跟@EnableAutoConfiguration的作用是差不多的糠涛,都是用來(lái)導(dǎo)入配置類(lèi)的
所以,總的來(lái)說(shuō)兼犯,這個(gè)用來(lái)跟配置中心交互的Spring容器最最主要就是干兩件事:
- 加載bootstrap配置文件
- 加載所有的
spring.factories
中的鍵為org.springframework.cloud.bootstrap.BootstrapConfiguration
對(duì)應(yīng)的配置類(lèi)
而在spring-cloud-context包下脱羡,@BootstrapConfiguration
會(huì)導(dǎo)入一個(gè)很重要的配置類(lèi)
PropertySourceBootstrapConfiguration
這個(gè)配置類(lèi)中會(huì)注入這么一個(gè)集合對(duì)象PropertySourceLocator
這個(gè)接口非常非常重要,先來(lái)看看注釋
Strategy for locating (possibly remote) property sources for the Environment. Implementations should not fail unless they intend to prevent the application from starting.
大致的意思是以一種策略的方式為Environment
定位(可能是遠(yuǎn)程)屬性配置(PropertySource
)免都。實(shí)現(xiàn)不應(yīng)該失敗,除非打算阻止應(yīng)用程序啟動(dòng)帆竹。
從這個(gè)翻譯后的意思就是說(shuō)绕娘,這個(gè)接口是用來(lái)定位,也就是說(shuō)獲取屬性配置的
并且可能是遠(yuǎn)程告訴我們一個(gè)很重要的信息栽连,那就是獲取的配置信息不僅僅可以存在本地险领,而且還可以存在遠(yuǎn)程侨舆。
遠(yuǎn)程?作者這里就差直接告訴你可以從配置中心
獲取了绢陌。挨下。
所以這個(gè)接口的作用就是用配置中心獲取配置的!
那么自然而然不同的配置中心要想整合到SpringCloud就得實(shí)現(xiàn)這個(gè)接口
當(dāng)注入完P(guān)ropertySourceLocator集合之后脐湾,在某個(gè)階段會(huì)調(diào)用所有的PropertySourceLocator臭笆,獲取配置中心中的配置
之后在把這些配置放到
Environment
中
這樣在ApplicationContext
的刷新階段就可以使用到配置中心的那些配置了
小總結(jié)
到這我們就弄明白了在項(xiàng)目啟動(dòng)中加載配置中心的配置了
其實(shí)就是項(xiàng)目在啟動(dòng)時(shí)會(huì)額外創(chuàng)建一個(gè)跟配置中心相關(guān)的Spring容器
這個(gè)容器會(huì)去加載bootstrap配置文件和所有的spring.factories
中的鍵為org.springframework.cloud.bootstrap.BootstrapConfiguration
對(duì)應(yīng)的配置類(lèi)
之后會(huì)去調(diào)用這個(gè)容器中所有的PropertySourceLocator
對(duì)象,從配置中心獲取配置
再放到Environment
中就完成了啟動(dòng)時(shí)從配置中心獲取配置的方式
最后秤掌,來(lái)張全家福概括一下前面整體的步驟
動(dòng)態(tài)刷新Bean的屬性
我們都知道愁铺,要想實(shí)現(xiàn)配置屬性的動(dòng)態(tài)刷新,需要在類(lèi)上加上一個(gè)注解@RefreshScope
重點(diǎn)來(lái)了
加了@RefreshScope
注解的Bean
闻鉴,就拿上圖中的UserService
舉例
Spring
在生成的時(shí)候會(huì)生成兩個(gè)UserService
的Bean
:
第一個(gè)是UserService
的代理動(dòng)態(tài)代理的Bean
茵乱,后面我稱(chēng)為第一個(gè)Bean
第二個(gè)就是UserService
這個(gè)Bean
,后面我稱(chēng)為第二個(gè)Bean
當(dāng)你在其它類(lèi)中需要注入一個(gè)UserService
時(shí)孟岛,真正注入的是第一個(gè)Bean
瓶竭,也就是動(dòng)態(tài)代理的Bean
當(dāng)你使用這個(gè)注入的動(dòng)態(tài)代理的Bean
的時(shí)候,他會(huì)去找第二個(gè)Bean
渠羞,也就是真正的UserService
這個(gè)Bean
斤贰,然后調(diào)用對(duì)應(yīng)的方法
比如你調(diào)用注入的UserService代理對(duì)象的getUsername方法,最終就會(huì)調(diào)用到第二個(gè)BeangetUsername方法
獲取到的username屬性值自然也就是第二個(gè)Bean中的username值
那么為什么要生成兩個(gè)Bean堵未?
接著往下瞅
在SpringCloud
中有這么一項(xiàng)規(guī)定
當(dāng)配置中心客戶(hù)端一旦感知到服務(wù)端的某個(gè)配置有變化的時(shí)候腋舌,需要發(fā)布一個(gè)RefreshEvent事件來(lái)告訴SpringCloud配置有變動(dòng)
在SpringCloud中RefreshEventListener類(lèi)會(huì)去監(jiān)聽(tīng)這個(gè)事件
一旦監(jiān)聽(tīng)到這個(gè)事件,SpringCloud會(huì)再次從配置中心拉取配置
這個(gè)拉取配置的核心邏輯跟啟動(dòng)時(shí)拉取配置的核心邏輯是一樣的
也是通過(guò) BootstrapApplicationListener 來(lái)實(shí)現(xiàn)的
這部分代碼邏輯在ContextRefresher類(lèi)中渗蟹,順著RefreshEventListener就能看到块饺,有興趣可以扒一扒
怕你忘了,我再把上面拉取配置的圖拿過(guò)來(lái)
有了最新的配置之后雌芽,就會(huì)進(jìn)行一步騷操作來(lái)移花接木”刷新“注入到對(duì)象的屬性
這個(gè)騷操作就是銷(xiāo)毀所有的前面提到的第二個(gè)Bean授艰,但是第一個(gè)Bean,也就是代理對(duì)象保持不變
當(dāng)程序運(yùn)行調(diào)用代理對(duì)象的方法的時(shí)候世落,發(fā)現(xiàn)第二個(gè)Bean沒(méi)有了淮腾,此時(shí)他就會(huì)去重新創(chuàng)建第二個(gè)Bean,也就是重新創(chuàng)建一個(gè)UserService對(duì)象
由于此時(shí)已經(jīng)拉到最新的配置了屉佳,也就是這個(gè)被重新創(chuàng)建的UserService對(duì)象注入的就是最新的屬性了
之后再調(diào)用的這個(gè)新創(chuàng)建的第二個(gè)Bean谷朝,拿到的自然就是最新的配置
所以,給你的感覺(jué)是對(duì)象的屬性發(fā)生了變化武花,實(shí)際上是真正被調(diào)用的對(duì)象重新創(chuàng)建了
所以這招移花接木還是有點(diǎn)意思的圆凰!
小總結(jié)
其實(shí)到這就弄明白了Bean的屬性動(dòng)態(tài)刷新的原理
其實(shí)就是當(dāng)配置中心客戶(hù)端發(fā)現(xiàn)服務(wù)端的配置有變化,需要發(fā)送一個(gè)RefreshEvent事件來(lái)告訴SpringCloud配置有變動(dòng)
SpringCloud會(huì)去監(jiān)聽(tīng)這個(gè)事件体箕,按照項(xiàng)目啟動(dòng)的方式重新拉取配置中心最新的屬性配置
當(dāng)拉取完屬性配置之后专钉,就會(huì)銷(xiāo)毀所有的第二個(gè)Bean挑童,也就是真正被使用的Bean
之后當(dāng)?shù)谝粋€(gè)Bean(動(dòng)態(tài)代理的Bean)需要使用這個(gè)第二個(gè)Bean時(shí),就會(huì)重新創(chuàng)建這個(gè)第二個(gè)Bean
此時(shí)由于已經(jīng)有最新的配置了跃须,那么創(chuàng)建的這個(gè)第二個(gè)Bean就會(huì)被注入最新的屬性站叼,這樣就實(shí)現(xiàn)了屬性的”刷新“
開(kāi)源配置中心是如何整合SpringCloud
首先我們?cè)賮?lái)梳理一下拉取配置和刷新配置的核心關(guān)鍵點(diǎn)
拉取配置關(guān)鍵點(diǎn)就是項(xiàng)目啟動(dòng)的時(shí)候(也包括重新拉取配置),會(huì)去創(chuàng)建一個(gè)容器
這個(gè)容器只讀取bootstrap配置文件和spring.factories中的鍵為org.springframework.cloud.bootstrap.BootstrapConfiguration對(duì)應(yīng)的配置類(lèi)
之后會(huì)獲取這個(gè)容器中的PropertySourceLocator菇民,從而獲取配置中心的配置
刷新配置關(guān)鍵點(diǎn)就是一旦配置中心配置變動(dòng)尽楔,就需要發(fā)送RefreshEvent事件,之后一系列刷新操作都是由SpringCloud的來(lái)完成的
所以玉雾,配置中心整合到SpringCloud其實(shí)就很簡(jiǎn)單翔试,就兩點(diǎn)
第一點(diǎn)就是需要實(shí)現(xiàn)
PropertySourceLocator
,并且配置中心一些相關(guān)的Bean需要通過(guò)org.springframework.cloud.bootstrap.BootstrapConfiguration
來(lái)裝配到這個(gè)容器中第二點(diǎn)复旬,當(dāng)配置發(fā)生變更需要發(fā)送
RefreshEvent
事件垦缅,這部分配置中心一些相關(guān)的Bean配置肯定是需要通過(guò)自動(dòng)裝配來(lái)完成
有了這兩點(diǎn)我們來(lái)看看Nacos
作為配置中心是如何整合到SpringCloud的
我們直接看Nacos的spring.factories文件
NacosConfigBootstrapConfiguration是用來(lái)實(shí)現(xiàn)第一點(diǎn)的
這個(gè)Bean就實(shí)現(xiàn)了PropertySourceLocator接口
第二點(diǎn)的實(shí)現(xiàn)就是通過(guò)NacosConfigAutoConfiguration配置類(lèi)來(lái)實(shí)現(xiàn)的
這里面有這么一個(gè)Bean
除了Nacos,比如說(shuō)Consul作為配置中心的時(shí)候也是這么一套實(shí)現(xiàn)邏輯
但是值的注意的是驹碍,像Apollo配置中心壁涎,他并沒(méi)有適配SpringCloud這套規(guī)范
當(dāng)然,如果你有興趣志秃,可以自己實(shí)現(xiàn)Apollo適配SpringCloud這套規(guī)范