一文带你理解@RefreshScope注解实现动态刷新原理

2023-10-29

概述

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

注解的作用

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

/**
 * Convenience annotation to put a <code>@Bean</code> 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")
@Documented
public @interface RefreshScope {
 
	/**
	 * @see Scope#proxyMode()
	 * @return proxy mode
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
 
}
  • 上面是RefreshScope的源码,该注解被@Scope注解使用,@Scope用来比较Spring Bean的作用域,具体使用参考相关文章。
  • 注解的属性proxyMode默认使用TARGET_CLASS作为代理。

实例

  1. controller中添加@RefreshScope
  2. nacos配置中心中配置
  3. 验证, 修改配置中心后,可以不重启动,刷新配置
  4. 去掉@RefreshScope 就不会自动刷新。

代码地址: github.com/alvinlkk/aw… 

原理解析

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

  1. 让Spring容器重新加载Environment环境配置变量
  2. Spring Bean重新创建生成

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

如果对Scope注解不清楚的可以阅读这篇文章:【Spring注解必知必会】@Scope注解源码解析

获取RefreshScope注解的Bean

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

  1. 因为Class被加上了@RefreshScope注解,那么这个BeanDefinition信息中的scope为refresh,在getBean的的时候会单独处理逻辑。
    public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
     
    protected <T> T doGetBean(
    			String name, @Nullable Class<T> 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缓存

  1. 配置中心发生变化后,会收到一个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<String> keys = this.refresh.refresh();
    			log.info("Refresh keys changed: " + keys);
    		}
    	}
     
    }
     
    // 这个是ContextRefresher类中的刷新方法
    public synchronized Set<String> refresh() {
            // 刷新spring的envirionment 变量配置
    		Set<String> keys = refreshEnvironment();
            // 刷新其他scope
    		this.scope.refreshAll();
    		return keys;
    	}
    

  2. refresh方法最终调用destroy方法,清空之前缓存的bean
public class RefreshScope extends GenericScope
		implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, 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<Throwable> errors = new ArrayList<Throwable>();
		Collection<BeanLifecycleWrapper> 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去深入理解。

————————————————
版权声明:本文为CSDN博主「肥肥技术宅」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_71777195/article/details/126319418

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一文带你理解@RefreshScope注解实现动态刷新原理 的相关文章

  • eclipse mylyn 与 redmine

    是否可以将mylyn连接到redmine而不需要redmine中的rest支持 有一个链接http download eclipse org mylyn incubator 3 8 http download eclipse org myl
  • Lombok 如何将代码生成到现有类中? [复制]

    这个问题在这里已经有答案了 我可以使用注释处理器从头开始生成类 但我无法像 lombok 那样修改类 我在 android studio 中搜索了 lombok 生成的类 但是我什么也没找到 然后我通过他们的网站检查了龙目岛概述 还在论坛中
  • 创建 RESTful WebService 并通过 Glassfish 4 提供服务

    我在 JEE6 中看到了很多关于 RESTful WebServices 的问题 所以我想与您分享这个示例解决方案 它展示了实现 RESTful Webservice 是多么容易 首先创建一个新的动态 Web 项目并将 Glassfish
  • RSA Java 加密和 Node.js 解密不起作用

    我有一个系统 需要在 javascript 中生成 RSA 密钥对 然后将公钥存储在服务器端的数据库中 作为字符串 然后 Java 中的服务器端将使用存储的公钥对字符串进行加密密钥并将其发送到客户端 客户端将使用私钥解密该字符串 我在客户端
  • Spring批处理2.2 JavaConfig

    我正在尝试让 Spring Batch 2 2 与 JavaConfig 一起使用 如今他们有一个 EnableBatchProcessing设置很多东西的注释 默认情况下 该注释使用数据源作为其作业数据 但我们不想保存此数据 也不想为其创
  • 在Java中,为什么equals()和hashCode()必须一致?

    如果我重写类上的任一方法 它必须确保如果A equals B true then A hashCode B hashCode也一定是真的 有人可以给我看一个简单的例子 如果违反了这一点 就会导致问题吗 我认为这与您是否使用该类作为 Hash
  • 在java中将HTML转换为RTF?

    我需要将 HTML 转换为 RTF 我正在使用以下代码 private static String convertToRTF String htmlStr OutputStream os new ByteArrayOutputStream
  • Spring Data JPA 规范继承

    我有三个实体 如下所示 Entity Inheritance strategy InheritanceType JOINED DiscriminatorColumn name type public abstract class Emplo
  • java应用程序,线程在终止MySQL连接后挂起

    我有一些工作线程正在运行 其中包括 MySQL 和 mysql connector java 5 1 20 当我杀死一些 SQL 语句 使用 mysql 客户端的kill 连接id 时 java线程挂起 这应该抛出一些异常 jstack 打
  • Selenium 和 xpath:查找带有类/id 的 div 并验证其中的文本

    我正在努力拥有xpath find a div并验证div有一个特定的string里面的文字 这是HTML div class Caption Model saved div and div class gwt HTML sfnStanda
  • 面临 process.start(); 的问题在 Android 棒棒糖中

    面临一个问题process start 在 Android 棒棒糖中 我在服务中遇到了 android lollipop 后台进程的问题 我的代码在 KitKat 之前工作正常 我有一个ProcessBuilder pBuilder并向其中
  • 识别包含本机方法实现的库文件/源

    如何识别包含本机方法实现的库文件 Ex public native String intern 我在哪里可以找到实施 source code of String intern 方法 找到了答案String intern 与快速谷歌搜索 ht
  • 菜单项标题未显示

    菜单项的标题未显示在片段内 我在菜单文件中有两个项目 第一个是带有图标和标签的showAsAction always在工具栏中显示图标 第二个只有标题 我不知道这里出了什么问题 菜单项的所有操作均有效 例如下面 菜单 销售 xml menu
  • 将 Tango 3D 点投影到屏幕 Google Project Tango

    Project Tango 提供了点云 如何获取点云中 3D 点的像素位置 以米为单位 我尝试使用投影矩阵 但得到的值非常小 0 5 1 3 等 而不是 1234 324 以像素为单位 我包含我尝试过的代码 Get the current
  • Java XPath API - 获取表示子树的字符串

    我的问题不是关于 xpath 语法 而是与 xpath 周围的 java API 有关 考虑以下 xml
  • Java中有没有办法随机获取HashMap的值?

    Java中有没有办法随机获取HashMap的值 这有效 Random generator new Random Object values myHashMap values toArray Object randomValue values
  • Scala 不可变 Map 速度慢

    当我创建地图时 我有一段代码 val map gtfLineArr 8 split map split collect case Array k v gt k v toMap 然后我使用这张地图来创建我的对象 case class MyOb
  • 如何处理JFreeChart中的SpiderWebPlot?

    发现 JFreeChart 我在使用时遇到了问题蜘蛛网情节 http www jfree org jfreechart api javadoc org jfree chart plot SpiderWebPlot html 这就是我今天所拥
  • DocumentBuilder 解析产生无效字节 2 of 4 字节 UTF-8 序列错误

    我正在尝试解析包含字符串的字节数组Impresi n in XML final DocumentBuilderFactory builderFactory DocumentBuilderFactory newInstance final D
  • java中的“main”可以返回字符串吗?

    java中的public static void main String args 是否有可能返回String代替void 如果是 怎么办 public static String main String args 代替 public st

随机推荐

  • RK3399交叉编译问题

    问题描述 老子拷贝同事的代码和RK3399文件包 妈md 出这个问题 解决 1 ls l usr lib aarch64 linux gnu libdl so 2 2 sudo rm usr lib aarch64 linux gnu li
  • 华为交换机在vlanif下绑定IP地址和MAC

    首先 我是在vlanif下启用dhcp的 2 1F Core1 Vlanif2 display this interface Vlanif2 ip address 172 16 15 1 255 255 254 0 dhcp select
  • 鸿蒙os和ios区别,华为鸿蒙OS和iOS以及安卓的区别到底在哪,一张图总结

    华为鸿蒙OS从2019年就在说了 很多人不了解什么是鸿蒙 也根本不知道这个OS和其它产品有什么区别 特别是未来华为想靠鸿蒙OS做什么 为什么华为愿意开源 为什么华为不像苹果一样做一个封闭的生态让自家产品的体验更好 今天借着下面这张图跟大家聊
  • 算法:跳跃游戏(js)

    题目 力扣 思路 试想一下 若从第三位置可以跳到第五位置 那只要前面有一个点能跳到第三位置即可说明整体能跳到第五位置 终点为最后一个点开始 看终点的前面是否存在一个点能跳到终点 若有这个点 此时终点为这个点 继续同样逻辑 代码 var ca
  • unsigned char和signed char型变量学习

    首先考虑下面代码输出什么 cpp view plain copy 1 include
  • Nginx 使用 HTTPS(准备证书和私钥)

    文章目录 Nginx生成自签名证书和配置Nginx HTTPS 准备证书和私钥 准备证书和私钥 Nginx生成自签名证书和配置Nginx HTTPS 准备证书和私钥 准备证书和私钥 生成私钥 openssl genrsa des3 out
  • 网络RJ45接口详解

    RJ45 简介 图 1 RJ45模块 RJ45模块用于实现PHY之间的互连 包括PHY芯片经信号变压器与RJ45接口相连 如图 1所示 RJ45连接器由插头和插座组成 RJ45插头又称水晶头 如图 3 10所示 这两种元件组成的连接器连接于
  • mmdetection训练数据遇到的问题

    1 Permission denied bash compile sh Permission denied 没有操作权限 改为赋予最高权限 777 chmod 777 compile sh 2 cuda问题 unable to execut
  • 无锁同步-C++11之Atomic和CAS

    首页 联系 订阅 无锁同步 C 11之Atomic和CAS 1 概要 本文是无锁同步系列文章的第一篇 主要探讨C 11中的Atomic 我们知道在C 11中引入了mutex和方便优雅的lock guard 但是有时候我们想要的是性能更高的无
  • 合并单元格

    protected void gridView PreRender object sender EventArgs e MergeRows gridView 合並單元格 public static void MergeRows GridVi
  • QT 子线程 更改UI

    一 问题原因 QT和Android类似 不能在子线程中直接更新UI 否则会有崩溃 二 实现方式 方式有几种 我这里只说一种 使用 信号机制 三 原理 原理其实是信号槽机制 槽连接方式 有以下四种 如果未设置默认 auto 即可跨线程通信 D
  • 蓝桥杯单片机(十三)AT24C02(EEPROM)

    AT24C02是一个2K位串行CMOS E2PROM 内部含有256个8位字节 CATALYST公司的先进CMOS技术实质上减少了器件的功耗 AT24C02有一个16字节页写缓冲器 该器件通过IIC总线接口进行操作 有一个专门的写保护功能
  • TL431应用电路与LTspice仿真

    TL431应用电路与LTspice仿真 文章目录 TL431应用电路 简介 应用电路1 稳压源 应用电路2 电压比较器 应用电路3 隔离型反馈电路 LTspice仿真 模型导入和使用 spice模型 原理图符号 放置模型 暂态仿真 DC扫描
  • 最大公约数,最小公倍数,素数等问题

    1 两个数的 最小公倍数 等于两个数的乘积除以最大公约数 scm a b a b gcd a b 所以主要是最大公约数问题 gcd 问题 辗转相除法 依据就是欧几里得定理 gcd a b gcd b a b def gcd a b whil
  • Docker三剑客——Compose

    前面介绍了Docker三剑客中的两个 今天我们介绍一下三剑客中的最后一个 docker compose 接下来的内容 我们还是从五个方面来讲解 主要包括如下 Compose简介 Compose安装与卸载 Compose常用命令 Compos
  • LinearEyeDepth 推导过程

    转载自 冯乐乐的 Unity Shader 入门精要 获取深度和法线纹理 虽然在Unity里获取深度和法线纹理的代码非常简单 但是我们有必要在这之前首先了解它们背后的实现原理 深度纹理实际上就是一张渲染纹理 只不过它里面存储的像素值不是颜色
  • 谈谈前华为荣耀软件测试工程师校招面试(已拿到offer)

    截止到现在 一共参加了2次笔试 2次面试 具体时间参照截图 机试 一共三道编程题 共500分好像 全对了一道 就是提交后通过测试 另外两道写完了 但是提交测试没通过 性格测试 就正常选了 保证前后一致啊 因为前面问到的后面还会有 不然会提示
  • 英语学习频道

    1 美国在线 www aol com 2 美国白宫 www whitehouse gov 3 路透网 中文 http cn reuters com 英文 http www reuters com 4 环球网 中文 http www huan
  • JS实现贪吃蛇

    JS实现贪吃蛇 1 结构 创建一个盒子box作为蛇的身体 当前盒子中只有一个子元素 代表此时蛇的长度为1 在创建一个盒子food作为贪吃蛇的食物 div div div div div div 2 CSS 设置蛇和食物的样式 这里注意蛇和食
  • 一文带你理解@RefreshScope注解实现动态刷新原理

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