概述
refeshscope这个注解想必大家都用过,在微服务配置中心的场景下经常出现,他可以用来刷新bean中的属性配置,那大家对他的实现原理了解吗?它为什么可以做到动态刷新呢?
注解的作用
@refreshscope注解是spring cloud中的一个注解,用来实现bean中属性的动态刷新。
/** * convenience annotation to put a @bean definition in * {@link org.springframework.cloud.context.scope.refresh.refreshscope refresh scope}. * beans annotated this way can be refreshed at runtime and any components that are using * them will get a new instance on the next method call, fully initialized and injected * with all dependencies. * * @author dave syer * */@target({ elementtype.type, elementtype.method })@retention(retentionpolicy.runtime)@scope(refresh)@documentedpublic @interface refreshscope { /** * @see scope#proxymode() * @return proxy mode */ scopedproxymode proxymode() default scopedproxymode.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的的时候会单独处理逻辑。
public abstract class abstractbeanfactory extends factorybeanregistrysupport implements configurablebeanfactory {protected t dogetbean( string name, @nullable class requiredtype, @nullable object[] args, boolean typecheckonly) throws beansexception { // 如果scope是单例的情况, 这里不进行分析 if (mbd.issingleton()) { ..... } // 如果scope是prototype的情况, 这里不进行分析 else if (mbd.isprototype()) { ...... } // 如果scope是其他的情况,本例中是reresh else { string scopename = mbd.getscope(); if (!stringutils.haslength(scopename)) { throw new illegalstateexception(no scope name defined for bean ' + beanname + '); } // 获取refresh scope的实现类refreshscope,这个类在哪里注入,我们后面讲 scope scope = this.scopes.get(scopename); if (scope == null) { throw new illegalstateexception(no scope registered for scope name ' + scopename + '); } try { // 这边是获取bean,调用的是refreshscope中的的方法 object scopedinstance = scope.get(beanname, () -> { beforeprototypecreation(beanname); try { return createbean(beanname, mbd, args); } finally { afterprototypecreation(beanname); } }); beaninstance = getobjectforbeaninstance(scopedinstance, name, beanname, mbd); } catch (illegalstateexception ex) { throw new scopenotactiveexception(beanname, scopename, ex); } } } catch (beansexception ex) { beancreation.tag(exception, ex.getclass().tostring()); beancreation.tag(message, string.valueof(ex.getmessage())); cleanupafterbeancreationfailure(beanname); throw ex; } finally { beancreation.end(); } } return adaptbeaninstance(name, beaninstance, requiredtype); } }
2.refreshscope继承成了genericscope类,最终调用的的是genericscope的get方法
public class genericscope implements scope, beanfactorypostprocessor, beandefinitionregistrypostprocessor, disposablebean { @override public object get(string name, objectfactory objectfactory) { // 将bean添加到缓存cache中 beanlifecyclewrapper value = this.cache.put(name, new beanlifecyclewrapper(name, objectfactory)); this.locks.putifabsent(name, new reentrantreadwritelock()); try { // 调用下面的getbean方法 return value.getbean(); } catch (runtimeexception e) { this.errors.put(name, e); throw e; } } private static class beanlifecyclewrapper { public object getbean() { // 如果bean为空,则创建bean if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectfactory.getobject(); } } } // 否则返回之前创建好的bean return this.bean; } } }
小结:
从这边的代码中可以印证了上面的说法,创建后的bean会缓存到scope的cache中,优先从缓存中获取,如果缓存中是null, 则重新走一遍create bean的流程。
refeshscope bean的创建
上面的在getbean的时候依赖到refreshscope这个bean,那么这个bean是在什么时候加入到spring bean中的呢?答案就是refreshautoconfiguration。
配置中心刷新后刷新bean缓存
配置中心发生变化后,会收到一个refreshevent事件,refresheventlistner监听器会监听到这个事件。
public class refresheventlistener implements smartapplicationlistener { ........ public void handle(refreshevent event) { if (this.ready.get()) { // don't handle events before app is ready log.debug(event received + event.geteventdesc()); // 会调用refresh方法,进行刷新 set keys = this.refresh.refresh(); log.info(refresh keys changed: + keys); } }}// 这个是contextrefresher类中的刷新方法public synchronized set refresh() { // 刷新spring的envirionment 变量配置 set keys = refreshenvironment(); // 刷新其他scope this.scope.refreshall(); return keys; }
refresh方法最终调用destroy方法,清空之前缓存的bean
public class refreshscope extends genericscope implements applicationcontextaware, applicationlistener, ordered { @managedoperation(description = dispose of the current instance of all beans + in this scope and force a refresh on next method execution.) public void refreshall() { // 调用父类的destroy super.destroy(); this.context.publishevent(new refreshscoperefreshedevent()); }}@override public void destroy() { list errors = new arraylist(); collection wrappers = this.cache.clear(); for (beanlifecyclewrapper wrapper : wrappers) { try { lock lock = this.locks.get(wrapper.getname()).writelock(); lock.lock(); try { // 这里主要就是把之前的bean设置为null, 就会重新走createbean的流程了 wrapper.destroy(); } finally { lock.unlock(); } } catch (runtimeexception e) { errors.add(e); } } if (!errors.isempty()) { throw wrapifnecessary(errors.get(0)); } this.errors.clear(); }
总结
上面是这个refreshscope实现动态刷新大致的原理,其中里面还有很多细节,可能需要留给大家自己debug去深入理解。
无线充电并不神秘,反而是鸡肋?
什么是脉冲电位器?它和普通电位器一样吗?
展望未来,把在ASIC世界更严格的工艺整合到FPGA
声学防水膜和声学防水网之间的区别是什么
U-SAM芯片组为日本非接触支付技术铺平道路
Nacos+@RefreshScope为什么配置能动态刷新?
了解LED 让你花更少的成本做出优美的设计
美光开始提供HBM2显存 或用于高性能显卡
ADI、世健、骏龙科技共同捐赠230万元助力湖北抗疫
多种触发功能的可编程高速数据采集模块
通信安全必学的十个基本技巧
温湿度传感器带来了更好的包装产品
我国工业控制领域信息安全态势又发展如何?
京东再次为低俗广告道歉
云计算市场竞争激烈 百度云借AI发力 浪潮云占坑
数字电视概述(DTV)
医疗器械与药物“异花受粉”带来的机遇与挑战
变速器故障及原因
S32K1XX系列keil下新建工程及验证
电池监控涉及到哪些技术 电池监控解决方案系统框图