概述
RefeshScope這個注解想必大家都用過,在微服務配置中心的場景下經常出現,他可以用來刷新Bean中的屬性配置,那大家對他的實現原理了解嗎?它為什么可以做到動態刷新呢?
注解的作用
@RefreshScope注解是Spring Cloud中的一個注解,用來實現Bean中屬性的動態刷新。
/**
*Convenienceannotationtoputa@Bean
definitionin
*{@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的作用域,具體使用參考相關文章。
注解的屬性proxyMode默認使用TARGET_CLASS作為代理。
實例
controller中添加@RefreshScope
nacos配置中心中配置
驗證, 修改配置中心后,可以不重啟動,刷新配置
去掉@RefreshScope 就不會自動刷新。
原理解析
為了實現動態刷新配置,主要就是想辦法達成以下兩個核心目標:
讓Spring容器重新加載Environment環境配置變量
Spring Bean重新創建生成
@RefreshScope主要就是基于@Scope注解的作用域代理的基礎上進行擴展實現的,加了@RefreshScope注解的類,在被Bean工廠創建后會加入自己的refresh scope 這個Bean緩存中,后續會優先從Bean緩存中獲取,當配置中心發生了變更,會把變更的配置更新到spring容器的Environment中,并且同事bean緩存就會被清空,從而就會從bean工廠中創建bean實例了,而這次創建bean實例的時候就會繼續經歷這個bean的生命周期,使得@Value屬性值能夠從Environment中獲取到最新的屬性值,這樣整個過程就達到了動態刷新配置的效果。
獲取RefreshScope注解的Bean
通過打上斷點查看堆棧可知:
因為Class被加上了@RefreshScope注解,那么這個BeanDefinition信息中的scope為refresh,在getBean的的時候會單獨處理邏輯。
publicabstractclassAbstractBeanFactoryextendsFactoryBeanRegistrySupportimplementsConfigurableBeanFactory{ protectedTdoGetBean( Stringname,@NullableClass requiredType,@NullableObject[]args,booleantypeCheckOnly) throwsBeansException{ //如果scope是單例的情況,這里不進行分析 if(mbd.isSingleton()){ ..... } //如果scope是prototype的情況,這里不進行分析 elseif(mbd.isPrototype()){ ...... } //如果scope是其他的情況,本例中是reresh else{ StringscopeName=mbd.getScope(); if(!StringUtils.hasLength(scopeName)){ thrownewIllegalStateException("Noscopenamedefinedforbean'"+beanName+"'"); } //獲取refreshscope的實現類RefreshScope,這個類在哪里注入,我們后面講 Scopescope=this.scopes.get(scopeName); if(scope==null){ thrownewIllegalStateException("NoScoperegisteredforscopename'"+scopeName+"'"); } try{ //這邊是獲取bean,調用的是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類,最終調用的的是GenericScope的get方法
publicclassGenericScope implementsScope,BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor,DisposableBean{ @Override publicObjectget(Stringname,ObjectFactory>objectFactory){ //將bean添加到緩存cache中 BeanLifecycleWrappervalue=this.cache.put(name,newBeanLifecycleWrapper(name,objectFactory)); this.locks.putIfAbsent(name,newReentrantReadWriteLock()); try{ //調用下面的getBean方法 returnvalue.getBean(); } catch(RuntimeExceptione){ this.errors.put(name,e); throwe; } } privatestaticclassBeanLifecycleWrapper{ publicObjectgetBean(){ //如果bean為空,則創建bean if(this.bean==null){ synchronized(this.name){ if(this.bean==null){ this.bean=this.objectFactory.getObject(); } } } //否則返回之前創建好的bean returnthis.bean; } } }
小結:
從這邊的代碼中可以印證了上面的說法,創建后的Bean會緩存到scope的cache中,優先從緩存中獲取,如果緩存中是null, 則重新走一遍create bean的流程。
RefeshScope Bean的創建
上面的在getBean的時候依賴到RefreshScope這個Bean,那么這個Bean是在什么時候加入到Spring Bean中的呢?答案就是RefreshAutoConfiguration。
配置中心刷新后刷新Bean緩存
配置中心發生變化后,會收到一個RefreshEvent事件,RefreshEventListner監聽器會監聽到這個事件。
publicclassRefreshEventListenerimplementsSmartApplicationListener{ ........ publicvoidhandle(RefreshEventevent){ if(this.ready.get()){//don'thandleeventsbeforeappisready log.debug("Eventreceived"+event.getEventDesc()); //會調用refresh方法,進行刷新 Setkeys=this.refresh.refresh(); log.info("Refreshkeyschanged:"+keys); } } } //這個是ContextRefresher類中的刷新方法 publicsynchronizedSet refresh(){ //刷新spring的envirionment變量配置 Set keys=refreshEnvironment(); //刷新其他scope this.scope.refreshAll(); returnkeys; }
refresh方法最終調用destroy方法,清空之前緩存的bean
publicclassRefreshScopeextendsGenericScope implementsApplicationContextAware,ApplicationListener,Ordered{ @ManagedOperation(description="Disposeofthecurrentinstanceofallbeans" +"inthisscopeandforcearefreshonnextmethodexecution.") publicvoidrefreshAll(){ //調用父類的destroy super.destroy(); this.context.publishEvent(newRefreshScopeRefreshedEvent()); } } @Override publicvoiddestroy(){ List errors=newArrayList (); Collection wrappers=this.cache.clear(); for(BeanLifecycleWrapperwrapper:wrappers){ try{ Locklock=this.locks.get(wrapper.getName()).writeLock(); lock.lock(); try{ //這里主要就是把之前的bean設置為null,就會重新走createBean的流程了 wrapper.destroy(); } finally{ lock.unlock(); } } catch(RuntimeExceptione){ errors.add(e); } } if(!errors.isEmpty()){ throwwrapIfNecessary(errors.get(0)); } this.errors.clear(); }
總結
上面是這個RefreshScope實現動態刷新大致的原理,其中里面還有很多細節,可能需要留給大家自己debug去深入理解。
審核編輯:劉清
-
cache技術
+關注
關注
0文章
41瀏覽量
1069 -
null
+關注
關注
0文章
19瀏覽量
3985
原文標題:Nacos+@RefreshScope 為什么配置能動態刷新?
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論