@RefreshScope 动态刷新机制

news2024/12/28 20:14:06

前言

一般在项目中,我们集成Nacos做统一配置管理,同时配置支持动态刷新,项目中都会用到@RefreshScope注解,这里和大家一起看看@RefreshScope实现动态刷新的原理。

@Scope注解

@RefreshScope 能实现动态刷新全仰仗着@Scope 这个注解,@Scope 代表了Bean的作用域,我们来看下其中的属性:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

	/**
	 * Alias for {@link #scopeName}.
	 * @see #scopeName
	 */
	@AliasFor("scopeName")
	String value() default "";

	/**
	 *  singleton  表示该bean是单例的。(默认)
     *  prototype    表示该bean是多例的,即每次使用该bean时都会新建一个对象。
     *  request        在一次http请求中,一个bean对应一个实例。
     *  session        在一个httpSession中,一个bean对应一个实例
	 */
	@AliasFor("value")
	String scopeName() default "";

	/**
    *   DEFAULT			不使用代理。(默认)
	* 	NO				不使用代理,等价于DEFAULT。
	* 	INTERFACES		使用基于接口的代理(jdk dynamic proxy)。
	* 	TARGET_CLASS	使用基于类的代理(cglib)。
    */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

通过代码我们可以清晰的看到两个主要属性value 和 proxyMode,value就不多说了,大家平时经常用看看注解就可以。proxyMode 就是@RefreshScope 实现的本质了。我们需要关心的就是ScopedProxyMode.TARGET_CLASS 这个属性,当ScopedProxyMode 为TARGET_CLASS 的时候会给当前创建的bean 生成一个代理对象,会通过代理对象来访问,每次访问都会创建一个新的对象。

@RefreshScope 源码

先看看@RefreshScope注解

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
	/**
	 * @see Scope#proxyMode()
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

 可以看出,它使用就是 @Scope ,其内部就一个属性默认 ScopedProxyMode.TARGET_CLASS。知道了是通过Spring Scope 来实现的那就简单了,我们来看下Scope 这个接口。

public interface Scope {

	/**
	 * Return the object with the given name from the underlying scope,
	 * {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
	 * if not found in the underlying storage mechanism.
	 * <p>This is the central operation of a Scope, and the only operation
	 * that is absolutely required.
	 * @param name the name of the object to retrieve
	 * @param objectFactory the {@link ObjectFactory} to use to create the scoped
	 * object if it is not present in the underlying storage mechanism
	 * @return the desired object (never {@code null})
	 * @throws IllegalStateException if the underlying scope is not currently active
	 */
	Object get(String name, ObjectFactory<?> objectFactory);

 
	@Nullable
	Object remove(String name);

 
	void registerDestructionCallback(String name, Runnable callback);

 
	@Nullable
	Object resolveContextualObject(String key);

	 
	@Nullable
	String getConversationId();

}

看下接口,我们只看Object get(String name, ObjectFactory<?> objectFactory); 这个方法帮助我们来创建一个新的bean ,也就是说,@RefreshScope 在调用 刷新的时候会使用此方法来给我们创建新的对象,这样就可以通过spring 的装配机制将属性重新注入了,也就实现了所谓的动态刷新。

因为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);
	}
    
}
复制代码

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的流程。

配置中心刷新后刷新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<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;
	}
复制代码

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();
	}
复制代码

配置动态刷新完整流程图

 参照上面图片,就可以了解清楚配置动态刷新的基础原理了。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/508380.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Matlab进阶绘图第19期—三角气泡热图

三角气泡热图&#xff0c;顾名思义&#xff0c;就是仅保留气泡热图数据矩阵的上三角或下三角部分。 三角气泡热图简单明了&#xff0c;通过不同颜色、不同大小的圆形表示数据的大小&#xff0c;可以更加直观地对矩阵数据进行可视化表达。 本文使用自制的tribubbleheatmap小工…

LSF/MM/BPF Summit 2023

5月8号&#xff0c;今年度的Linux Storage, Filesystem, Memory Management & BPF Summit已经拉开帷幕&#xff0c;Linux存储、文件系统、内存管理以及BPF领域的年度峰会又一次到来。此次峰会聚集了Linux最重要的开发专家以及内核子系统维护者&#xff0c;以规划和探索改进…

Arcgis Server/GeoServer服务启动后内存使用高,系统卡

说明 ArcGIS Server和geoserver本质上都是Tomcat服务&#xff0c;所以只需要设置Tomcat最大堆大小和最大内存大小就可以。此方法通用与Tomcat自身配置。 配置文件位置 一.Tomcat 安装目录/bin 二.ArcGIS Server .\ArcGIS\Server\framework\runtime\tomcat\bin 三.Geoserver …

【代码随想录】刷题Day22

1.二叉搜索树的公共祖先 235. 二叉搜索树的最近公共祖先 不同于普通二叉树&#xff0c;二叉搜索树得益于其顺序结构&#xff0c;其公共祖先的查找也有迹可循。自顶向下递归遍历&#xff0c;只要一个节点的val夹在p和q之间&#xff0c;那么该节点就是最近公共祖先。 1.首先公共…

HTML 中的常用标签用法

目录 1.基本结构 2.注释标签 3.标题标签 4.换行标签 5.格式化标签 6.图片标签 7.超链接标签 8.表格标签 合并单元格 9.列表标签 10.表单标签 form标签 input标签 补充 无语义标签 总 HTML是一种超文本标记语言,在网站上看到的信息都是它实现的.(是由标签所构…

RK3588平台开发系列讲解(内存篇)Linux 伙伴系统数据结构

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、 页二、区三、内存节点沉淀、分享、成长,让自己和他人都能有所收获!😄 📢Linux 系统中,用来管理物理内存页面的伙伴系统,以及负责分配比页更小的内存对象的 SLAB 分配器了。 本篇将介绍伙伴系统相关数据结…

M304A_S905L3-B_5621无线蓝牙_当贝纯净桌面-线刷固件包

M304A_S905L3-B_5621无线蓝牙_当贝纯净桌面-线刷固件包 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&#xff0c;运…

springboot第19集:权限

article 文章表sys_permission 后台权限表sys_role 后台角色表sys_role_permission 角色-权限关联表sys_user 用户表sys_user_role 用户-角色关联表 image.png image.png sys_user_role id user_id(用户id) role_id(角色id) sys_role id role_name(角色名) create_time(创建时间…

决策树生成剪枝算法原理

决策树生成算法 首先明确信息熵 信息增益的概念 信息增益表示得知特征X信息是的类Y的信息不确定性减少的程度 H(D) 经验熵表示对数据D进行分类的不确定性 H(D|A)经验条件熵表示对特征A给定条件下对数据集D进行分类的不确定性&#xff08;显然这个值越小越好 那么g(D,A)信息…

基于粒子群优化的中文文本分类

基本思路&#xff1a; 方法&#xff1a;使用优化算法&#xff08;如粒子群&#xff09;优化支持向量机SVM&#xff1b; 本文所使用的应用背景&#xff1a;中文文本分类&#xff08;同时可以应用到其他背景领域&#xff0c;如&#xff09; 应用背景&#xff08;元启发式算法优…

(学习日记)2023.5.9

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

QTableview常用几种代理总结

QTableview常用几种代理总结 [1] QTableview常用几种代理总结1、QCheckBox和QRadioButton的嵌入2、QHeadView中嵌入QCheckBox类3、QCombobox的嵌入4、 QCombox QCheckBox类5、SpinBox的嵌入类6、QProcess的嵌入类7、QProcess绘制版本的嵌入类8、QPushButton/QLabel/QImage的嵌…

鸿蒙Hi3861学习八-Huawei LiteOS-M(事件标记)

一、简介 事件是一种实现任务间通信的机制&#xff0c;可用于实现任务间的同步。但事件通信只能是事件类型的通信&#xff0c;无数据传输。一个任务可以等待多个事件的发生&#xff1a;可以是任意一个事件发生时唤醒任务进行事件处理&#xff1b;也可以是几个事件都发生后才唤醒…

mongodb副本集搭建

1.本次搭建使用三台centos7主机搭建集群&#xff0c;关闭防火墙和selinux服务 2.主机信息如下图所示 主机名称IPPortServiceA10.1.60.11427017mongodbB10.1.60.11527017mongodbC10.1.60.11827017mongodb 3.从官网下载mongodb安装包(我这里下载的是6.0.5版本的tgz包) Instal…

小家电LED显示驱动多功能语音芯片IC方案 WT2003H4 B002

随着时代的进步&#xff0c;智能家电的普及已经成为了一个趋势。而在智能家电中&#xff0c;LED显示屏也成为了不可或缺的一部分。因此&#xff0c;在小家电的设计中&#xff0c;LED显示驱动芯片的应用也越来越广泛。比如&#xff1a;电饭煲、电磁炉、数字时钟、咖啡机、电磁炉…

【Vue3】如何创建Vue3项目及组合式API

文章目录 前言 一、如何创建vue3项目&#xff1f; ①使用 vue-cli 创建 ②使用可视化ui创建 ③npm init vite-app ④npm init vuelatest 二、 API 风格 2.1 选项式 API (Options API) 2.2 组合式 API (Composition API) 总结 前言 例如&#xff1a;随着前端领域的不断发展&am…

【SSM框架】SpringMVC 中常见的注解和用法

SSM框架 SpringMVC 中常见的注解和用法基础注解介绍RequestMapping 注解介绍PostMapping 和 GetMapping 注解介绍 获取参数相关注解的介绍只通过 RequestMapping 来获取参数只传递一个参数传递对象参数传递多个参数(非对象) RequestParam 后端参数重命名required 必传参数的设置…

SpringBoot+Redis+自定义注解实现接口防刷(限制不同接口单位时间内最大请求次数)

场景 SpringBoot搭建的项目需要对开放的接口进行防刷限制&#xff0c;不同接口指定多少秒内可以请求指定次数。 比如下方限制接口一秒内最多请求一次。 注&#xff1a; 博客&#xff1a;霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主 实现 1、实现思路 首…

flink学习37:DataStream/DataSet与Table的互相转换

DataStream/DataSet转换成视图 DataStream/DataSet转换成表 表转换成DataStream/DataSet 表转换为DataStream/DataSet时&#xff0c;需要指定字段数据类型&#xff0c;最方便的就是把数据类型定为row&#xff0c;即行数据。 两种模式&#xff1a; 把表转为dataStream 把表转为d…

100ASK-V853-PRO编译烧写

100ASK_V853-PRO 环境配置及编译烧写 0.前言 本章主要介绍关于100ASK_V853-PRO开发板的Tina SDK包的下载和编译打包生成镜像&#xff0c;并将镜像烧录到100ASK_V853-PRO开发板上。在进行100ASK_V853-PRO开发板的环境配置前需要获取配置虚拟机系统&#xff0c;可以参考&#x…