大家好,我是三友~~
這篇文章來扒一扒SpringCloud配置中心的核心原理。
不知你是否跟我一樣,在剛開始使用SpringCloud配置中心的時候也有很多的疑惑:
本文就通過探討上述問題來探秘SpringCloud配置中心核心的底層原理。
在SpringBoot啟動的時候會經歷一系列步驟,核心就是SpringApplication的run方法的邏輯
圖片
整個過程大致可以劃分為三個階段:
圖片
ApplicationContext刷新前階段,這個階段主要也干三件事
圖片
ApplicationContext刷新階段,這個階段其實就是調用ApplicationContext#refresh方法來刷新容器
圖片
刷新的整個過程可以看我之前寫的萬字+20張圖剖析Spring啟動時12個核心步驟這篇文章
ApplicationContext刷新后階段,這個階段其實就是收尾的階段,這個過程其實沒有什么非常核心的事
ok,在說完上面這三個階段之后,思考一個問題
你覺得在上面的三個階段,哪個階段最有可能從配置中心拉取配置?
其實稍微思考一下,肯定是想到的就是刷新前階段
因為我已經明示了,準備Environment
玩笑歸玩笑,為什么是這個階段?
很好理解,因為這個階段是準備Environment,也就是準備外部化配置
只需要在這個階段加載配置中心的配置,放到Environment中,后面在整個ApplicationContext刷新階段創建Bean的時候,就可以使用到配置中心的配置了
其實不光是配置中心的配置,比如配置文件的配置,也是在這里階段讀取的
至于如何實現的,我們接著往下瞅
上一節得出一個結論
準備Environment,也就是prepareEnvironment方法的實現,是拉取配置的核心
prepareEnvironment方法
不過在說這個方法之前,先來講一下一些前置操作
在SpringApplication創建的時候,會去加載spring.factories中的一些對象,其中就包括:
SpringApplicationRunListener僅僅只有一個實現EventPublishingRunListener
構造的時候會創建一個SimpleApplicationEventMulticaster,再將加載的ApplicationListener添加進去
SimpleApplicationEventMulticaster是用來發布事件用的,不清楚的話可以看三萬字盤點Spring 9大核心基礎功能這篇文章
按照傳統,畫張圖來理一下這部分前置操作
圖片
接著來講一下prepareEnvironment方法
prepareEnvironment方法
這個方法會首先創建一個Environment對象
之后會執行這么一行方法,傳入剛剛創建的Environment對象
圖片
這個方法最終會發布一個ApplicationEnvironmentPreparedEvent事件
而對這個事件有兩個特別重要的監聽器:
這些監聽器都是通過前置操作從spring.factories配置文件中加載的
ConfigFileApplicationListener,用來處理配置文件的,他會解析配置文件的配置,放到Environment中
BootstrapApplicationListener這個跟本文探討的主題相關了,它是用來專門來跟配置中心交互的
到這,我們就找到了SpringCloud配置中心配置拉取的整個入口邏輯
不過在分析BootstrapApplicationListener是如何從配置中心拉取配置的之前,先來張圖總結一下這部分prepareEnvironment的操作
圖片
在BootstrapApplicationListener中,他首先也會創建一個SpringApplication去執行
圖片
其實本質上就是創建一個Spring容器,也就是ApplicationContext
這個容器非常重要,這個容器是專門用來跟配置中心交互的
這個容器在創建的時候會給它兩個比較重要的配置
第一個就是設置這個容器所用的配置文件的名稱
圖片
默認就是bootstrap
這就解釋了為什么配置中心的配置信息需要寫在bootstrap配置文件中
第二個就是會加入一個配置類
圖片
這個配置類又會通過@Import注解導入另一個配置類
圖片
BootstrapImportSelector實現了(間接)ImportSelector接口
那么這個容器在啟動的時候,就會調用BootstrapImportSelector的selectImports方法的實現獲取到一些配置類
而BootstrapImportSelector的selectImports實現從截圖中也就可以看出
他會加載所有的spring.factories中的鍵為org.springframework.cloud.bootstrap.BootstrapConfiguration的配置類
其實這里@BootstrapConfiguration的作用其實跟@EnableAutoConfiguration的作用是差不多的,都是用來導入配置類的
所以,總的來說,這個用來跟配置中心交互的Spring容器最最主要就是干兩件事:
圖片
而在spring-cloud-context包下,@BootstrapConfiguration會導入一個很重要的配置類
圖片
圖片
這個配置類中會注入這么一個集合對象
PropertySourceLocator
這個接口非常非常重要,先來看看注釋
Strategy for locating (possibly remote) property sources for the Environment. Implementations should not fail unless they intend to prevent the application from starting.
我用我的四級英語功力給大家翻譯一下
以一種策略的方式為Environment定位(可能是遠程)屬性配置(PropertySource)。實現不應該失敗,除非打算阻止應用程序啟動。
從這個翻譯后的意思就是說,這個接口是用來定位,也就是說獲取屬性配置的
并且可能是遠程告訴我們一個很重要的信息,那就是獲取的配置信息不僅僅可以存在本地,而且還可以存在遠程。
遠程?作者這里就差直接告訴你可以從配置中心獲取了。。
所以這個接口的作用就是用配置中心獲取配置的!
那么自然而然不同的配置中心要想整合到SpringCloud就得實現這個接口
當注入完PropertySourceLocator集合之后,在某個階段會調用所有的PropertySourceLocator,獲取配置中心中的配置
之后在把這些配置放到Environment中
這樣在ApplicationContext的刷新階段就可以使用到配置中心的那些配置了
到這我們就弄明白了在項目啟動中加載配置中心的配置了
其實就是項目在啟動時會額外創建一個跟配置中心相關的Spring容器
這個容器會去加載bootstrap配置文件和所有的spring.factories中的鍵為org.springframework.cloud.bootstrap.BootstrapConfiguration對應的配置類
之后會去調用這個容器中所有的PropertySourceLocator對象,從配置中心獲取配置
再放到Environment中就完成了啟動時從配置中心獲取配置的方式
最后,來張全家福概括一下前面整體的步驟
圖片
我們都知道,要想實現配置屬性的動態刷新,需要在類上加上一個注解
@RefreshScope
圖片
重點來了
加了@RefreshScope注解的Bean,就拿上圖中的UserService舉例
Spring在生成的時候會生成兩個UserService的Bean:
當你在其它類中需要注入一個UserService時,真正注入的是第一個Bean,也就是動態代理的Bean
當你使用這個注入的動態代理的Bean的時候,他會去找第二個Bean,也就是真正的UserService這個Bean,然后調用對應的方法
圖片
比如你調用注入的UserService代理對象的getUsername方法,最終就會調用到第二個BeangetUsername方法
獲取到的username屬性值自然也就是第二個Bean中的username值
那么為什么要生成兩個Bean?
接著往下瞅
在SpringCloud中有這么一項規定
當配置中心客戶端一旦感知到服務端的某個配置有變化的時候,需要發布一個RefreshEvent事件來告訴SpringCloud配置有變動
圖片
在SpringCloud中RefreshEventListener類會去監聽這個事件
RefreshEventListener
一旦監聽到這個事件,SpringCloud會再次從配置中心拉取配置
這個拉取配置的核心邏輯跟啟動時拉取配置的核心邏輯是一樣的
也是通過 BootstrapApplicationListener 來實現的
圖片
這部分代碼邏輯在ContextRefresher類中,順著RefreshEventListener就能看到,有興趣可以扒一扒
怕你忘了,我再把上面拉取配置的圖拿過來
圖片
有了最新的配置之后,就會進行一步騷操作來移花接木”刷新“注入到對象的屬性
這個騷操作就是銷毀所有的前面提到的第二個Bean,但是第一個Bean,也就是代理對象保持不變
圖片
當程序運行調用代理對象的方法的時候,發現第二個Bean沒有了,此時他就會去重新創建第二個Bean,也就是重新創建一個UserService對象
由于此時已經拉到最新的配置了,也就是這個被重新創建的UserService對象注入的就是最新的屬性了
圖片
之后再調用的這個新創建的第二個Bean,拿到的自然就是最新的配置
所以,給你的感覺是對象的屬性發生了變化,實際上是真正被調用的對象重新創建了
所以這招移花接木還是有點意思的!
其實到這就弄明白了Bean的屬性動態刷新的原理
其實就是當配置中心客戶端發現服務端的配置有變化,需要發送一個RefreshEvent事件來告訴SpringCloud配置有變動
SpringCloud會去監聽這個事件,按照項目啟動的方式重新拉取配置中心最新的屬性配置
當拉取完屬性配置之后,就會銷毀所有的第二個Bean,也就是真正被使用的Bean
之后當第一個Bean(動態代理的Bean)需要使用這個第二個Bean時,就會重新創建這個第二個Bean
此時由于已經有最新的配置了,那么創建的這個第二個Bean就會被注入最新的屬性,這樣就實現了屬性的”刷新“
圖片
上面大致說了@RefreshScope動態刷新的原理
這里我補充一下@RefreshScope代碼層面的實現原理
本來這部分原理我是寫在前面的,但是我發現這塊比較繞,怕打斷文章的節奏,所以就準備刪除了
但是想想既然都寫了,那么就給放到補充里面吧,看不懂也不耽誤前面的理解
圖片
這個注解是個衍生注解,真正起作用的就是@Scope注解
@Scope注解并不陌生,他其實是定義Bean的作用域
比如多例(原型),就可以加上@Scope("prototype")注解
還有一些八股文常背的作用域,比如session作用域等等
而@RefreshScope也可以看做是一種Bean的作用域,名字叫做refresh
這些除了單例和多例之外的作用域的底層實現邏輯都是一樣的
這些帶有作用域的Bean相比于普通的單例Bean主要有以下幾點不同:
先說會注冊兩個Bean,還是以前面提到的UserService舉個例子,這兩個Bean分別是
第一個Bean的class為ScopedProxyFactoryBean,是個FactoryBean的實現
圖片
這個最終會生成一個代理對象,上面的例子就是為UserService生成一個代理對象,并且由于是單例的,所以最終這個對象會被放到一級緩存中,我們使用時注入的也就是這個對象
第二個Bean的class是UserService,所以生成的就是真正的UserService對象,但是由于scope為refresh,所以不會存在第一級緩存中
這部分注冊兩個Bean的代碼是在ScopedProxyUtils#createScopedProxy方法中,有興趣的可以扒扒
再來講一講保存的地方不同
不同的作用域都需要實現一個Scope接口來存放對應的Bean
圖片
比如refresh、session作用域都有對應的實現
圖片
也就是通過Scope就可以管理不同作用域的Bean
所以,對于refresh這個作用域來說,他的所有的Bean都在RefreshScope中
后面說的銷毀,只需要移除RefreshScope中的Bean就可以了
圖片
首先我們再來梳理一下拉取配置和刷新配置的核心關鍵點
拉取配置關鍵點就是項目啟動的時候(也包括重新拉取配置),會去創建一個容器
這個容器只讀取bootstrap配置文件和spring.factories中的鍵為org.springframework.cloud.bootstrap.BootstrapConfiguration對應的配置類
之后會獲取這個容器中的PropertySourceLocator,從而獲取配置中心的配置
刷新配置關鍵點就是一旦配置中心配置變動,就需要發送RefreshEvent事件,之后一系列刷新操作都是由SpringCloud的來完成的
所以,配置中心整合到SpringCloud其實就很簡單,就兩點
第一點就是需要實現PropertySourceLocator,并且配置中心一些相關的Bean需要通過org.springframework.cloud.bootstrap.BootstrapConfiguration來裝配到這個容器中
第二點,當配置發生變更需要發送RefreshEvent事件,這部分配置中心一些相關的Bean配置肯定是需要通過自動裝配來完成
有了這兩點我們來看看Nacos作為配置中心是如何整合到SpringCloud的
我們直接看Nacos的spring.factories文件
圖片
NacosConfigBootstrapConfiguration是用來實現第一點的
圖片
除了Nacos自己的一些Bean,他還聲明了一個NacosPropertySourceLocator這個Bean
NacosPropertySourceLocator
這個Bean就實現了PropertySourceLocator接口
第二點的實現就是通過NacosConfigAutoConfiguration配置類來實現的
這里面有這么一個Bean
圖片
這個Bean就實現了配置變化發送事件的操作
圖片
除了Nacos,比如說Consul作為配置中心的時候也是這么一套實現邏輯
但是值的注意的是,像Apollo配置中心,他并沒有適配SpringCloud這套規范
當然,如果你有興趣,可以自己實現Apollo適配SpringCloud這套規范
本文鏈接:http://www.tebozhan.com/showinfo-26-13640-0.html8000字+22張圖探秘SpringCloud配置中心的核心原理
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
下一篇: 從0手寫一個多線程日志包