SpringCloud 配置中心的核心原理

不知你是否跟我一樣蚓耽,在剛開(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è)階段:

run方法流程

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的操作

事件派發(fā)過(guò)程

SpringCloud巧妙地拉取配置

在BootstrapApplicationListener中纹蝴,他首先也會(huì)創(chuàng)建一個(gè)SpringApplication去執(zhí)行


創(chuàng)建一個(gè)SpringApplication

其實(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)

bootstrap名稱(chēng)

默認(rèn)就是bootstrap

這就解釋了為什么配置中心的配置信息需要寫(xiě)在bootstrap配置文件中

第二個(gè)就是會(huì)加入一個(gè)配置類(lèi)

BootstrapImportSelectorConfiguration

注冊(cè)一個(gè)導(dǎo)入配置類(lèi)

這個(gè)配置類(lèi)又會(huì)通過(guò)@Import注解導(dǎo)入另一個(gè)配置類(lèi)BootstrapImportSelector

`
注冊(cè)BootstrapConfiguration子類(lèi)

BootstrapImportSelector實(shí)現(xiàn)了(間接)ImportSelector接口

那么這個(gè)容器在啟動(dòng)的時(shí)候塘安,就會(huì)調(diào)用BootstrapImportSelectorselectImports方法的實(shí)現(xiàn)獲取到一些配置類(lèi)

BootstrapImportSelectorselectImports實(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)

自動(dòng)配置類(lèi)

PropertySourceBootstrapConfiguration
PropertySourceBootstrapConfiguration

這個(gè)配置類(lèi)中會(huì)注入這么一個(gè)集合對(duì)象PropertySourceLocator

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臭笆,獲取配置中心中的配置

加載配置過(guò)程

之后在把這些配置放到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)張全家福概括一下前面整體的步驟


整體過(guò)程

動(dòng)態(tài)刷新Bean的屬性

我們都知道愁铺,要想實(shí)現(xiàn)配置屬性的動(dòng)態(tài)刷新,需要在類(lèi)上加上一個(gè)注解@RefreshScope

bean

重點(diǎn)來(lái)了

加了@RefreshScope注解的Bean闻鉴,就拿上圖中的UserService舉例

Spring在生成的時(shí)候會(huì)生成兩個(gè)UserServiceBean

第一個(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)用過(guò)程

比如你調(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)

s屬性變更

在SpringCloud中RefreshEventListener類(lèi)會(huì)去監(jiān)聽(tīng)這個(gè)事件


刷新監(jiān)聽(tīng)器

一旦監(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)


刷新配置全過(guò)程

有了最新的配置之后雌芽,就會(huì)進(jìn)行一步騷操作來(lái)移花接木”刷新“注入到對(duì)象的屬性

這個(gè)騷操作就是銷(xiāo)毀所有的前面提到的第二個(gè)Bean授艰,但是第一個(gè)Bean,也就是代理對(duì)象保持不變


銷(xiāo)毀掉目標(biāo)對(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ì)象注入的就是最新的屬性了

新建目標(biāo)對(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)了屬性的”刷新“


刷新bean的全過(guò)程

開(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文件


Nacos的自動(dòng)注入

NacosConfigBootstrapConfiguration是用來(lái)實(shí)現(xiàn)第一點(diǎn)的

屬性器實(shí)現(xiàn)

這個(gè)Bean就實(shí)現(xiàn)了PropertySourceLocator接口

第二點(diǎn)的實(shí)現(xiàn)就是通過(guò)NacosConfigAutoConfiguration配置類(lèi)來(lái)實(shí)現(xiàn)的

這里面有這么一個(gè)Bean


內(nèi)容刷新bean

除了Nacos,比如說(shuō)Consul作為配置中心的時(shí)候也是這么一套實(shí)現(xiàn)邏輯

但是值的注意的是驹碍,像Apollo配置中心壁涎,他并沒(méi)有適配SpringCloud這套規(guī)范

當(dāng)然,如果你有興趣志秃,可以自己實(shí)現(xiàn)Apollo適配SpringCloud這套規(guī)范

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萌狂,一起剝皮案震驚了整個(gè)濱河市箫踩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖看杭,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糯俗,死亡現(xiàn)場(chǎng)離奇詭異禁熏,居然都是意外死亡敏簿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)洼冻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)崭歧,“玉大人,你說(shuō)我怎么就攤上這事撞牢÷誓耄” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵屋彪,是天一觀(guān)的道長(zhǎng)所宰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)畜挥,這世上最難降的妖魔是什么仔粥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮砰嘁,結(jié)果婚禮上件炉,老公的妹妹穿的比我還像新娘。我一直安慰自己矮湘,他們只是感情好斟冕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著缅阳,像睡著了一般磕蛇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上十办,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天秀撇,我揣著相機(jī)與錄音,去河邊找鬼向族。 笑死呵燕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的件相。 我是一名探鬼主播再扭,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼夜矗!你這毒婦竟也來(lái)了泛范?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤紊撕,失蹤者是張志新(化名)和其女友劉穎罢荡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體对扶,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡区赵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辩稽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惧笛。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逞泄,靈堂內(nèi)的尸體忽然破棺而出患整,到底是詐尸還是另有隱情,我是刑警寧澤喷众,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布各谚,位于F島的核電站,受9級(jí)特大地震影響到千,放射性物質(zhì)發(fā)生泄漏昌渤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一憔四、第九天 我趴在偏房一處隱蔽的房頂上張望膀息。 院中可真熱鬧般眉,春花似錦、人聲如沸潜支。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)冗酿。三九已至埠对,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裁替,已是汗流浹背项玛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弱判,地道東北人襟沮。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像裕循,于是被迫代替她去往敵國(guó)和親臣嚣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容