概述
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浏览量
1062 -
null
+关注
关注
0文章
18浏览量
3967
原文标题:Nacos+@RefreshScope 为什么配置能动态刷新?
文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论