0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Nacos+@RefreshScope为什么配置能动态刷新?

jf_ro2CN3Fa 来源:JAVA旭阳 2023-05-19 14:15 次阅读

概述

RefeshScope这个注解想必大家都用过,在微服务配置中心的场景下经常出现,他可以用来刷新Bean中的属性配置,那大家对他的实现原理了解吗?它为什么可以做到动态刷新呢?

注解的作用

@RefreshScope注解是Spring Cloud中的一个注解,用来实现Bean中属性的动态刷新。

/**
*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的作用域,具体使用参考相关文章。

注解的属性proxyMode默认使用TARGET_CLASS作为代理。

实例

controller中添加@RefreshScope

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

nacos配置中心中配置

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

验证, 修改配置中心后,可以不重启动,刷新配置

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

去掉@RefreshScope 就不会自动刷新。

原理解析

为了实现动态刷新配置,主要就是想办法达成以下两个核心目标:

让Spring容器重新加载Environment环境配置变量

Spring Bean重新创建生成

@RefreshScope主要就是基于@Scope注解的作用域代理的基础上进行扩展实现的,加了@RefreshScope注解的类,在被Bean工厂创建后会加入自己的refresh scope 这个Bean缓存中,后续会优先从Bean缓存中获取,当配置中心发生了变更,会把变更的配置更新到spring容器的Environment中,并且同事bean缓存就会被清空,从而就会从bean工厂中创建bean实例了,而这次创建bean实例的时候就会继续经历这个bean的生命周期,使得@Value属性值能够从Environment中获取到最新的属性值,这样整个过程就达到了动态刷新配置的效果。

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

获取RefreshScope注解的Bean

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

通过打上断点查看堆栈可知:

因为Class被加上了@RefreshScope注解,那么这个BeanDefinition信息中的scope为refresh,在getBean的的时候会单独处理逻辑。

publicabstractclassAbstractBeanFactoryextendsFactoryBeanRegistrySupportimplementsConfigurableBeanFactory{

protectedTdoGetBean(
Stringname,@NullableClassrequiredType,@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,ObjectFactoryobjectFactory){
//将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。

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

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

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

配置中心发生变化后,会收到一个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类中的刷新方法
publicsynchronizedSetrefresh(){
//刷新spring的envirionment变量配置
Setkeys=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(){
Listerrors=newArrayList();
Collectionwrappers=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 为什么配置能动态刷新?

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

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

    Nacos 是阿里巴巴今年7月份开源的项目,如其名, Naming Configuration Service ,专注于服务发现和配置管理领域。本系列文章,将从 5W1H(What、Where
    的头像 发表于 10-29 08:53 1.5w次阅读

    Nacos的概念和功能

    1、Nacos简介 Nacos的概念和功能 Nacos是一个面向微服务架构的动态服务发现、配置管理和服务治理平台,它能够帮助开发人员和运维人
    的头像 发表于 09-25 11:02 2320次阅读

    支持Dubbo生态发展,阿里巴巴启动新的开源项目 Nacos

    ,其核心定位是 “一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台”。Nacos 有三大主要功能:服务发现与服务管理在采用以“服务(Service)”为中心的诸如微服务及云原生方式的现代
    发表于 07-05 17:35

    结合场景谈一谈微服务配置

    ,最终是抽象为一个个的配置项,要想实现运行时的动态调整阈值和开关的启停,将这些配置项存放到 Nacos配置模块中最适合不过了。在今年 8
    发表于 12-12 15:53

    构建ARM64版本nacos docker镜像

    在适配过程中有大量合作伙伴用到nacos且采用容器化部署,dockerhub未提供官方镜像,因此需要在鲲鹏服务器自定义构建。构建前提:Docker已部署构建步骤:1、下载包含构建所需的脚本下载完成
    发表于 06-16 14:29

    基于71M6515H和双CPU的智能动态分相无功补偿控制器

    基于71M6515H和双CPU的智能动态分相无功补偿控制器摘要:为了改善电能质量,设计了一种基于71M6515H 和双CPU 的智能动态分相无功补偿控制器,利用71M6515H 解决了
    发表于 05-12 16:10 32次下载

    基于LabView平台的齿轮箱性能动态测试与诊断_李贵明

    基于LabView平台的齿轮箱性能动态测试与诊断_李贵明
    发表于 03-18 09:41 3次下载

    Nacos发布0.5.0版本,轻松玩转动态 DNS 服务

    Gateway 动态路由配置,等到这块足够成熟,会将其包含在Nacos的官方推荐实现中。四、TTL & Health Status Aggregation在Nacos之前的几个版本中
    发表于 12-05 16:22 119次阅读

    微服务配置中心实战:Spring + MyBatis + Druid + Nacos

    在 结合场景谈服务发现和配置 中我们讲述了 Nacos 配置中心的三个典型的应用场景,包括如何在 Spring Boot 中使用 Nacos 配置
    发表于 12-29 17:09 1104次阅读
    微服务<b class='flag-5'>配置</b>中心实战:Spring + MyBatis + Druid + <b class='flag-5'>Nacos</b>

    Nacos服务地址动态感知原理

    Nacos Server:Nacos服务提供者,里面包含的Open API是功能访问入口,Conig Service、Naming Service 是Nacos提供的配置服务、命名服务
    的头像 发表于 09-26 10:40 1762次阅读

    Nacos为什么这么强?Nacos注册中心的底层原理,从服务注册到服务发现

    来源:码猿技术专栏 1. Nacos介绍 2. Nacos注册中心实现原理分析 2.1 Nacos架构图 2.2 注册中心的原理 3. Nacos源码分析 3.1
    的头像 发表于 10-08 16:46 1.2w次阅读

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

    传统的静态配置方式要想修改某个配置只能修改之后重新发布应用,要实现动态性,可以选择使用数据库,通过定时轮询访问数据库来感知配置的变化。轮询频率低感知
    的头像 发表于 10-31 11:14 1408次阅读

    华为云CSE 关键特性,支持托管Nacos注册配置中心

    于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,
    的头像 发表于 12-29 16:23 1018次阅读
    华为云CSE 关键特性,支持托管<b class='flag-5'>Nacos</b>注册<b class='flag-5'>配置</b>中心

    基于Nacos的简单动态化线程池实现

    本文以Nacos作为服务配置中心,以修改线程池核心线程数、最大线程数为例,实现一个简单的动态化线程池。
    发表于 01-06 14:14 862次阅读

    Nacos实现原理:SpringCloud集成Nacos的实现过程

    Nacos服务提供者,里面包含的Open API是功能访问入口,Conig Service、Naming Service 是Nacos提供的配置服务、命名服务模块。Consitency
    发表于 10-09 16:08 1070次阅读
    <b class='flag-5'>Nacos</b>实现原理:SpringCloud集成<b class='flag-5'>Nacos</b>的实现过程