深入理解SpringCloud一(4)Bean中的属性是如何刷新的?
上文说到Nacos配置中心文件变动通知,本文继续讲解后续动作,Bean中的属性值是如何刷新的。 //发布RefreshEvent事件 applicationContext.publishEvent( new RefreshEvent(this, null, "Refresh Nacos config"));一、@RefreshScope介绍
要想动态刷新Bean中的属性值,Class上必须注解@RefreshScope,这个注解又是干什么的呢。
我们看一下@RefreshScope的源码,实际就是@Scope("refresh"),代理方式使用CGLIB。@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Scope("refresh") @Documented public @interface RefreshScope { ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; }二、AnnotationScopeMetadataResolver介绍
@Scope注解的解析方法如下,解析出Bean的scope和代理模式。@Override public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { ScopeMetadata metadata = new ScopeMetadata(); if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor( annDef.getMetadata(), this.scopeAnnotationType); if (attributes != null) { metadata.setScopeName(attributes.getString("value")); ScopedProxyMode proxyMode = attributes.getEnum("proxyMode"); if (proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = this.defaultProxyMode; } metadata.setScopedProxyMode(proxyMode); } } return metadata; }三、RefreshEventListener处理刷新事件的入口@Override public void onApplicationEvent(ApplicationEvent event) { //省略部分代码 else if (event instanceof RefreshEvent) { handle((RefreshEvent) event); } } public void handle(RefreshEvent event) { if (this.ready.get()) { Set keys = this.refresh.refresh(); } }
最终调用ContextRefresher的refresh方法。public synchronized Set refresh() { Set keys = refreshEnvironment(); this.scope.refreshAll(); return keys; }
refreshEnvironment,刷新Environment里面的属性值,然后发布EnvironmentChangeEvent事件,里面包括了变动的key。我们可以通过监听这个事情,获得变动的配置key。public synchronized Set refreshEnvironment() { //获取旧的key value 值 Map before = extract(this.context.getEnvironment().getPropertySources()); //重新加载配置到当前Environment中 addConfigFilesToEnvironment(); //对比获取变动的key Set keys = changes(before,extract(this.context.getEnvironment().getPropertySources())).keySet(); this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys)); return keys; }
addConfigFilesToEnvironment,通过SpringApplication的构建,重新走一遍配置加载流程,获取所有的配置,然后更新到当前Context的Environment中。 ConfigurableApplicationContext addConfigFilesToEnvironment() { ConfigurableApplicationContext capture = null; try { //从当前环境copy出一份 StandardEnvironment environment = copyEnvironment(this.context.getEnvironment()); //通过SpringApplication重新走一遍配置加载流程 SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class) .bannerMode(Mode.OFF).web(WebApplicationType.NONE) .environment(environment); builder.application() .setListeners(Arrays.asList(new BootstrapApplicationListener(), new ConfigFileApplicationListener())); capture = builder.run(); if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) { environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE); } MutablePropertySources target = this.context.getEnvironment().getPropertySources(); String targetName = null; //拷贝到我们的context Environment中 for (PropertySource<?> source : environment.getPropertySources()) { String name = source.getName(); if (target.contains(name)) { targetName = name; } if (!this.standardSources.contains(name)) { if (target.contains(name)) { target.replace(name, source); } else { if (targetName != null) { target.addAfter(targetName, source); // update targetName to preserve ordering targetName = name; } else { // targetName was null so we are at the start of the list target.addFirst(source); targetName = name; } } } } }//省略部分代码 return capture; }
至此Context的Environment已经是最新的了,但是Bean中的属性值还没有被刷新。
设置最新的Environment后,继续调用RefreshScope.refreshAll(),将@RefreshScope注解的Bean,进行destroy,然后发布RefreshScopeRefreshedEvent事件。public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); }
我们看一下destroy,并没有看到刷新Bean属性值的方法。@Override public void destroy() { List errors = new ArrayList(); Collection wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { //删除部分代码 try { wrapper.destroy(); } } }
this.cache.clear()也没有刷新Bean属性值的方法,只是最终由StandardScopeCache实现的,使用ConcurrentMap做缓存清理。
我们需要了解AbstractBeanFactory中scope的Bean是如何创建的,才能解开谜团。String scopeName = mbd.getScope(); Scope scope = this.scopes.get(scopeName); try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); }
Scope是一个接口,RefreshScope是其中的一个实现,这里实际就是调用RefreshScope的get(String name, ObjectFactory<?> objectFactory)方法创建Bean。RefreshScope继承GenericScope,最终调用代码如下@Override public Object get(String name, ObjectFactory<?> objectFactory) { BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } }
我们看到创建的时候有this.cache.put(),配置刷新Bean销毁的时候有this.cache.clear(),玄机就在这里。
如果缓存中有BeanLifecycleWrapper对象则返回旧对象,否则放入缓存中。public Object put(String name, Object value) { Object result = this.cache.putIfAbsent(name, value); if (result != null) { return result; } return value; }
我们再看BeanLifecycleWrapper.getBean()方法,标准的单例写法,双重检查加锁创建bean。public Object getBean() { if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; }
至此我们了解到,通过RefreshScope创建bean后,会进行缓存(通过BeanLifecycleWrapper实现),如果没有刷新配置,则一直使用缓存,当配置刷新时清除缓存,RefreshScope会重新创建bean,这时bean中的属性就是最新的了。
四、总结
1.通过临时SpringApplication的构建,重新走一遍配置加载流程,获取所有的配置,然后更新到当前Context的Environment中。
2.通过RefreshScope控制Bean的生命周期,在配置刷新的时候,重建Bean对象。
为提高公司整体市场竞争力,德众汽车拟斥资1000万元设立全资子公司1月17日,资本邦了解到,北交所上市公司德众汽车(838030。BJ)于近日发布了对外投资的公告。公告显示,湖南德远新能源汽车集团有限公司(系湖南德众汽车销售服务股份有限公司全资子
烧钱后买菜平台路在何方作者李勇坚中国社会科学院财经战略研究院研究员历经烧钱大战后,处于持续亏损状态的网络买菜平台正在冷静下来,一些地方区域供应商订单快速下降,用户流失严重,网络买菜平台正面临新的挑战。网
龙湖总部更名为赋能平台在宣布成立地产航道外,龙湖还对集团总部进行轻量化设置,更名为集团赋能平台,设立战略研究部供应链管理部公共事务部品牌部数字科技部人力资源及行政部财务部法律事务部等职能。业内人士称,这
激战手机存量市场一加并入OPPO后渠道技术多元融合,人群再细分21世纪经济报道记者骆轶琪广州报道自2017年高点至今,中国的手机行业已经进入存量市场多年。在马太效应之下,头部大厂之间正开始激烈地巷战。其中一个表现是,各大品牌都在针对细分用户群
微软宣布687亿美元收购动视暴雪据报道,微软宣布将以每股95美元的价格收购游戏巨头动视暴雪,全现金交易总价值687亿美元。微软表示,交易完成后,微软将成为世界上收入第三高的游戏公司,这笔收购将包括动视(Activ
京东居家送装不打烊服务上线!春节焕新家迎新年一点不耽搁虎年春节临近,不少人都希望趁着难得的假期给自家添新换件,以新面貌新气象迎接新一年。比如,给孩子购置一张儿童学习桌椅,给经常忘记带钥匙的老人换上智能密码锁,给自己挑一张可调节背撑的电
十余家企业互联互通,春节吃喝玩乐一个App搞定来源长沙晚报网长沙晚报掌上长沙1月17日讯(全媒体记者吴鑫矾)1月17日,百度联合美团小红书顺丰携程知乎同程猫眼58同城等十余家企业宣布开启互联互通深度合作,以春节为起点,在流量技
发展新电商打造新吉货春节将至,由朱琳主导的多场年货节直播活动在各地轮番登场不仅卖小米,还卖小米做成的糖把10斤1包的大块牛肉分割包装冷链运输售卖,单价更低复购率更高卖杂粮粥的同时,推荐佐餐的地产咸鸭蛋
浏览器基础HTMLDOM如果需要动态改变页面上的元素,实现页面元素的添加移除和修改,甚至是重排,就需要获得能够对HTML文档中所有元素进行访问的入口,这个入口就是文档对象模型,简称DOM浏览
三星开始良心,骁龙888IP68无线充电,这才是我要的小屏旗舰随着智能手机的发展,如今屏幕是越来越大,主流来到了6。7英寸的水平,很多人都纷纷感叹拿不过来了,电子产品的便携性倒退许多,这让发烧友们都表示,想要拥有一款小屏旗舰,各大厂商肯定是听
好家云店智能播货人交出年度报告,日播素材1亿条促进GMV增长好家云店作为一家具有强技术基因的私域电商SaaS服务云平台,拥有数百名技术及研发人员,团队核心人员来自于淘宝早期团队,经历过双11千亿GMV规模的考验,有丰富的电商系统研发经验。已