0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Nacos+@RefreshScope為什么配置能動態(tài)刷新?

jf_ro2CN3Fa ? 來源:JAVA旭陽 ? 2023-05-19 14:15 ? 次閱讀

概述

RefeshScope這個(gè)注解想必大家都用過,在微服務(wù)配置中心的場景下經(jīng)常出現(xiàn),他可以用來刷新Bean中的屬性配置,那大家對他的實(shí)現(xiàn)原理了解嗎?它為什么可以做到動態(tài)刷新呢?

注解的作用

@RefreshScope注解是Spring Cloud中的一個(gè)注解,用來實(shí)現(xiàn)Bean中屬性的動態(tài)刷新。

/**
*Convenienceannotationtoputa@Beandefinitionin
*{@linkorg.springframework.cloud.context.scope.refresh.RefreshScoperefreshscope}.
*Beansannotatedthiswaycanberefreshedatruntimeandanycomponentsthatareusing
*themwillgetanewinstanceonthenextmethodcall,fullyinitializedandinjected
*withalldependencies.
*
*@authorDaveSyer
*
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public@interfaceRefreshScope{

/**
*@seeScope#proxyMode()
*@returnproxymode
*/
ScopedProxyModeproxyMode()defaultScopedProxyMode.TARGET_CLASS;

}

上面是RefreshScope的源碼,該注解被@Scope注解使用,@Scope用來比較Spring Bean的作用域,具體使用參考相關(guān)文章。

注解的屬性proxyMode默認(rèn)使用TARGET_CLASS作為代理。

實(shí)例

controller中添加@RefreshScope

1d47d002-f5fc-11ed-90ce-dac502259ad0.png

nacos配置中心中配置

1d6e088a-f5fc-11ed-90ce-dac502259ad0.png

驗(yàn)證, 修改配置中心后,可以不重啟動,刷新配置

1d839e70-f5fc-11ed-90ce-dac502259ad0.png1d9adefa-f5fc-11ed-90ce-dac502259ad0.png1db9bad2-f5fc-11ed-90ce-dac502259ad0.png

去掉@RefreshScope 就不會自動刷新。

原理解析

為了實(shí)現(xiàn)動態(tài)刷新配置,主要就是想辦法達(dá)成以下兩個(gè)核心目標(biāo):

讓Spring容器重新加載Environment環(huán)境配置變量

Spring Bean重新創(chuàng)建生成

@RefreshScope主要就是基于@Scope注解的作用域代理的基礎(chǔ)上進(jìn)行擴(kuò)展實(shí)現(xiàn)的,加了@RefreshScope注解的類,在被Bean工廠創(chuàng)建后會加入自己的refresh scope 這個(gè)Bean緩存中,后續(xù)會優(yōu)先從Bean緩存中獲取,當(dāng)配置中心發(fā)生了變更,會把變更的配置更新到spring容器的Environment中,并且同事bean緩存就會被清空,從而就會從bean工廠中創(chuàng)建bean實(shí)例了,而這次創(chuàng)建bean實(shí)例的時(shí)候就會繼續(xù)經(jīng)歷這個(gè)bean的生命周期,使得@Value屬性值能夠從Environment中獲取到最新的屬性值,這樣整個(gè)過程就達(dá)到了動態(tài)刷新配置的效果。

1dd650f2-f5fc-11ed-90ce-dac502259ad0.png

獲取RefreshScope注解的Bean

1e5a8502-f5fc-11ed-90ce-dac502259ad0.png

通過打上斷點(diǎn)查看堆??芍?/p>

因?yàn)镃lass被加上了@RefreshScope注解,那么這個(gè)BeanDefinition信息中的scope為refresh,在getBean的的時(shí)候會單獨(dú)處理邏輯。

publicabstractclassAbstractBeanFactoryextendsFactoryBeanRegistrySupportimplementsConfigurableBeanFactory{

protectedTdoGetBean(
Stringname,@NullableClassrequiredType,@NullableObject[]args,booleantypeCheckOnly)
throwsBeansException{

//如果scope是單例的情況,這里不進(jìn)行分析
if(mbd.isSingleton()){
.....
}
//如果scope是prototype的情況,這里不進(jìn)行分析
elseif(mbd.isPrototype()){
......
}
//如果scope是其他的情況,本例中是reresh
else{
StringscopeName=mbd.getScope();
if(!StringUtils.hasLength(scopeName)){
thrownewIllegalStateException("Noscopenamedefinedforbean'"+beanName+"'");
}
//獲取refreshscope的實(shí)現(xiàn)類RefreshScope,這個(gè)類在哪里注入,我們后面講
Scopescope=this.scopes.get(scopeName);
if(scope==null){
thrownewIllegalStateException("NoScoperegisteredforscopename'"+scopeName+"'");
}
try{
//這邊是獲取bean,調(diào)用的是RefreshScope中的的方法
ObjectscopedInstance=scope.get(beanName,()->{
beforePrototypeCreation(beanName);
try{
returncreateBean(beanName,mbd,args);
}
finally{
afterPrototypeCreation(beanName);
}
});
beanInstance=getObjectForBeanInstance(scopedInstance,name,beanName,mbd);
}
catch(IllegalStateExceptionex){
thrownewScopeNotActiveException(beanName,scopeName,ex);
}
}
}
catch(BeansExceptionex){
beanCreation.tag("exception",ex.getClass().toString());
beanCreation.tag("message",String.valueOf(ex.getMessage()));
cleanupAfterBeanCreationFailure(beanName);
throwex;
}
finally{
beanCreation.end();
}
}

returnadaptBeanInstance(name,beanInstance,requiredType);
}

}

2.RefreshScope繼承成了GenericScope類,最終調(diào)用的的是GenericScope的get方法

publicclassGenericScope
implementsScope,BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor,DisposableBean{
@Override

publicObjectget(Stringname,ObjectFactoryobjectFactory){
//將bean添加到緩存cache中
BeanLifecycleWrappervalue=this.cache.put(name,newBeanLifecycleWrapper(name,objectFactory));
this.locks.putIfAbsent(name,newReentrantReadWriteLock());
try{
//調(diào)用下面的getBean方法
returnvalue.getBean();
}
catch(RuntimeExceptione){
this.errors.put(name,e);
throwe;
}
}

privatestaticclassBeanLifecycleWrapper{

publicObjectgetBean(){
//如果bean為空,則創(chuàng)建bean
if(this.bean==null){
synchronized(this.name){
if(this.bean==null){
this.bean=this.objectFactory.getObject();
}
}
}
//否則返回之前創(chuàng)建好的bean
returnthis.bean;
}
}
}

小結(jié):

從這邊的代碼中可以印證了上面的說法,創(chuàng)建后的Bean會緩存到scope的cache中,優(yōu)先從緩存中獲取,如果緩存中是null, 則重新走一遍create bean的流程。

RefeshScope Bean的創(chuàng)建

上面的在getBean的時(shí)候依賴到RefreshScope這個(gè)Bean,那么這個(gè)Bean是在什么時(shí)候加入到Spring Bean中的呢?答案就是RefreshAutoConfiguration。

1e98f7b0-f5fc-11ed-90ce-dac502259ad0.png

配置中心刷新后刷新Bean緩存

1ec8ccd8-f5fc-11ed-90ce-dac502259ad0.png

配置中心發(fā)生變化后,會收到一個(gè)RefreshEvent事件,RefreshEventListner監(jiān)聽器會監(jiān)聽到這個(gè)事件。

publicclassRefreshEventListenerimplementsSmartApplicationListener{


........

publicvoidhandle(RefreshEventevent){
if(this.ready.get()){//don'thandleeventsbeforeappisready
log.debug("Eventreceived"+event.getEventDesc());
//會調(diào)用refresh方法,進(jìn)行刷新
Setkeys=this.refresh.refresh();
log.info("Refreshkeyschanged:"+keys);
}
}

}

//這個(gè)是ContextRefresher類中的刷新方法
publicsynchronizedSetrefresh(){
//刷新spring的envirionment變量配置
Setkeys=refreshEnvironment();
//刷新其他scope
this.scope.refreshAll();
returnkeys;
}

refresh方法最終調(diào)用destroy方法,清空之前緩存的bean

publicclassRefreshScopeextendsGenericScope
implementsApplicationContextAware,ApplicationListener,Ordered{

@ManagedOperation(description="Disposeofthecurrentinstanceofallbeans"
+"inthisscopeandforcearefreshonnextmethodexecution.")
publicvoidrefreshAll(){
//調(diào)用父類的destroy
super.destroy();
this.context.publishEvent(newRefreshScopeRefreshedEvent());
}
}


@Override
publicvoiddestroy(){
Listerrors=newArrayList();
Collectionwrappers=this.cache.clear();
for(BeanLifecycleWrapperwrapper:wrappers){
try{
Locklock=this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try{
//這里主要就是把之前的bean設(shè)置為null,就會重新走createBean的流程了
wrapper.destroy();
}
finally{
lock.unlock();
}
}
catch(RuntimeExceptione){
errors.add(e);
}
}
if(!errors.isEmpty()){
throwwrapIfNecessary(errors.get(0));
}
this.errors.clear();
}

總結(jié)

上面是這個(gè)RefreshScope實(shí)現(xiàn)動態(tài)刷新大致的原理,其中里面還有很多細(xì)節(jié),可能需要留給大家自己debug去深入理解。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • cache技術(shù)
    +關(guān)注

    關(guān)注

    0

    文章

    41

    瀏覽量

    1064
  • null
    +關(guān)注

    關(guān)注

    0

    文章

    19

    瀏覽量

    3974

原文標(biāo)題:Nacos+@RefreshScope 為什么配置能動態(tài)刷新?

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Nacos是什么?Nacos配置管理技巧你知道嗎

    Nacos 是阿里巴巴今年7月份開源的項(xiàng)目,如其名, Naming Configuration Service ,專注于服務(wù)發(fā)現(xiàn)和配置管理領(lǐng)域。本系列文章,將從 5W1H(What、Where
    的頭像 發(fā)表于 10-29 08:53 ?1.5w次閱讀

    Nacos的概念和功能

    1、Nacos簡介 Nacos的概念和功能 Nacos是一個(gè)面向微服務(wù)架構(gòu)的動態(tài)服務(wù)發(fā)現(xiàn)、配置管理和服務(wù)治理平臺,它能夠幫助開發(fā)人員和運(yùn)維人
    的頭像 發(fā)表于 09-25 11:02 ?2337次閱讀

    支持Dubbo生態(tài)發(fā)展,阿里巴巴啟動新的開源項(xiàng)目 Nacos

    ,其核心定位是 “一個(gè)更易于幫助構(gòu)建云原生應(yīng)用的動態(tài)服務(wù)發(fā)現(xiàn)、配置和服務(wù)管理平臺”。Nacos 有三大主要功能:服務(wù)發(fā)現(xiàn)與服務(wù)管理在采用以“服務(wù)(Service)”為中心的諸如微服務(wù)及云原生方式的現(xiàn)代
    發(fā)表于 07-05 17:35

    結(jié)合場景談一談微服務(wù)配置

    ,最終是抽象為一個(gè)個(gè)的配置項(xiàng),要想實(shí)現(xiàn)運(yùn)行時(shí)的動態(tài)調(diào)整閾值和開關(guān)的啟停,將這些配置項(xiàng)存放到 Nacos配置模塊中最適合不過了。在今年 8
    發(fā)表于 12-12 15:53

    構(gòu)建ARM64版本nacos docker鏡像

    在適配過程中有大量合作伙伴用到nacos且采用容器化部署,dockerhub未提供官方鏡像,因此需要在鯤鵬服務(wù)器自定義構(gòu)建。構(gòu)建前提:Docker已部署構(gòu)建步驟:1、下載包含構(gòu)建所需的腳本下載完成
    發(fā)表于 06-16 14:29

    基于71M6515H和雙CPU的智能動態(tài)分相無功補(bǔ)償控制器

    基于71M6515H和雙CPU的智能動態(tài)分相無功補(bǔ)償控制器摘要:為了改善電能質(zhì)量,設(shè)計(jì)了一種基于71M6515H 和雙CPU 的智能動態(tài)分相無功補(bǔ)償控制器,利用71M6515H 解決了
    發(fā)表于 05-12 16:10 ?32次下載

    基于LabView平臺的齒輪箱性能動態(tài)測試與診斷_李貴明

    基于LabView平臺的齒輪箱性能動態(tài)測試與診斷_李貴明
    發(fā)表于 03-18 09:41 ?3次下載

    Nacos發(fā)布0.5.0版本,輕松玩轉(zhuǎn)動態(tài) DNS 服務(wù)

    Gateway 動態(tài)路由配置,等到這塊足夠成熟,會將其包含在Nacos的官方推薦實(shí)現(xiàn)中。四、TTL & Health Status Aggregation在Nacos之前的幾個(gè)版本中
    發(fā)表于 12-05 16:22 ?119次閱讀

    微服務(wù)配置中心實(shí)戰(zhàn):Spring + MyBatis + Druid + Nacos

    在 結(jié)合場景談服務(wù)發(fā)現(xiàn)和配置 中我們講述了 Nacos 配置中心的三個(gè)典型的應(yīng)用場景,包括如何在 Spring Boot 中使用 Nacos 配置
    發(fā)表于 12-29 17:09 ?1104次閱讀
    微服務(wù)<b class='flag-5'>配置</b>中心實(shí)戰(zhàn):Spring + MyBatis + Druid + <b class='flag-5'>Nacos</b>

    Nacos服務(wù)地址動態(tài)感知原理

    Nacos Server:Nacos服務(wù)提供者,里面包含的Open API是功能訪問入口,Conig Service、Naming Service 是Nacos提供的配置服務(wù)、命名服務(wù)
    的頭像 發(fā)表于 09-26 10:40 ?1770次閱讀

    Nacos為什么這么強(qiáng)?Nacos注冊中心的底層原理,從服務(wù)注冊到服務(wù)發(fā)現(xiàn)

    來源:碼猿技術(shù)專欄 1. Nacos介紹 2. Nacos注冊中心實(shí)現(xiàn)原理分析 2.1 Nacos架構(gòu)圖 2.2 注冊中心的原理 3. Nacos源碼分析 3.1
    的頭像 發(fā)表于 10-08 16:46 ?1.2w次閱讀

    Nacos、Apollo、Config配置中心如何選型?

    傳統(tǒng)的靜態(tài)配置方式要想修改某個(gè)配置只能修改之后重新發(fā)布應(yīng)用,要實(shí)現(xiàn)動態(tài)性,可以選擇使用數(shù)據(jù)庫,通過定時(shí)輪詢訪問數(shù)據(jù)庫來感知配置的變化。輪詢頻率低感知
    的頭像 發(fā)表于 10-31 11:14 ?1422次閱讀

    華為云CSE 關(guān)鍵特性,支持托管Nacos注冊配置中心

    于構(gòu)建云原生應(yīng)用的動態(tài)服務(wù)發(fā)現(xiàn)、配置管理和服務(wù)管理平臺。 Nacos 幫助您發(fā)現(xiàn)、配置和管理微服務(wù)。Nacos?提供了一組簡單易用的特性集,
    的頭像 發(fā)表于 12-29 16:23 ?1024次閱讀
    華為云CSE 關(guān)鍵特性,支持托管<b class='flag-5'>Nacos</b>注冊<b class='flag-5'>配置</b>中心

    基于Nacos的簡單動態(tài)化線程池實(shí)現(xiàn)

    本文以Nacos作為服務(wù)配置中心,以修改線程池核心線程數(shù)、最大線程數(shù)為例,實(shí)現(xiàn)一個(gè)簡單的動態(tài)化線程池。
    發(fā)表于 01-06 14:14 ?864次閱讀

    Nacos實(shí)現(xiàn)原理:SpringCloud集成Nacos的實(shí)現(xiàn)過程

    Nacos服務(wù)提供者,里面包含的Open API是功能訪問入口,Conig Service、Naming Service 是Nacos提供的配置服務(wù)、命名服務(wù)模塊。Consitency
    發(fā)表于 10-09 16:08 ?1085次閱讀
    <b class='flag-5'>Nacos</b>實(shí)現(xiàn)原理:SpringCloud集成<b class='flag-5'>Nacos</b>的實(shí)現(xiàn)過程