SpringCloud @RefreshScope刷新机制浅析

目录
  • 一、前言
  • 二、@Scope
  • 三、RefreshScope 的实现原理
  • 四、总结

一、前言

用过Spring Cloud的同学都知道在使用动态配置刷新的我们要配置一个@RefreshScope 在类上才可以实现对象属性的的动态更新,本着知其所以然的态度,晚上没事儿又把这个点回顾了一下,下面就来简单的说下自己的理解。

总览下,实现@RefreshScope 动态刷新的就需要以下几个:

  • @ Scope
  • @RefreshScope
  • RefreshScope
  • GenericScope
  • Scope
  • ContextRefresher

二、@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;
}

2. 可以看出,它使用就是 @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 的装配机制将属性重新注入了,也就实现了所谓的动态刷新。

那它究竟是怎么处理老的对象,又怎么除法创建新的对象呢?

在开头我提过几个重要的类,而其中 RefreshScope extends GenericScope, GenericScope implements Scope。

所以通过查看代码,是GenericScope 实现了 Scope 最重要的 get(String name, ObjectFactory<?> objectFactory) 方法,在GenericScope 里面 包装了一个内部类 BeanLifecycleWrapperCache 来对加了 @RefreshScope 从而创建的对象进行缓存,使其在不刷新时获取的都是同一个对象。(这里你可以把 BeanLifecycleWrapperCache 想象成为一个大Map 缓存了所有@RefreshScope 标注的对象)

知道了对象是缓存的,所以在进行动态刷新的时候,只需要清除缓存,重新创建就好了。

来看代码,眼见为实,只留下关键方法:

// ContextRefresher 外面使用它来进行方法调用 ============================== 我是分割线
	public synchronized Set<String> refresh() {
		Set<String> keys = refreshEnvironment();
		this.scope.refreshAll();
		return keys;
	}
// RefreshScope 内部代码  ============================== 我是分割线
	@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
	public void refreshAll() {
		super.destroy();
		this.context.publishEvent(new RefreshScopeRefreshedEvent());
	}
// GenericScope 里的方法 ============================== 我是分割线
	//进行对象获取,如果没有就创建并放入缓存
	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
		BeanLifecycleWrapper value = this.cache.put(name,
				new BeanLifecycleWrapper(name, objectFactory));
		locks.putIfAbsent(name, new ReentrantReadWriteLock());
		try {
			return value.getBean();
		}
		catch (RuntimeException e) {
			this.errors.put(name, e);
			throw e;
		}
	}
	//进行缓存的数据清理
	@Override
	public void destroy() {
		List<Throwable> errors = new ArrayList<Throwable>();
		Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
		for (BeanLifecycleWrapper wrapper : wrappers) {
			try {
				Lock lock = locks.get(wrapper.getName()).writeLock();
				lock.lock();
				try {
					wrapper.destroy();
				}
				finally {
					lock.unlock();
				}
			}
			catch (RuntimeException e) {
				errors.add(e);
			}
		}
		if (!errors.isEmpty()) {
			throw wrapIfNecessary(errors.get(0));
		}
		this.errors.clear();
	}

通过观看源代码我们得知,我们截取了三个片段所得之,ContextRefresher 就是外层调用方法用的,GenericScope 里面的 get 方法负责对象的创建和缓存,destroy 方法负责再刷新时缓存的清理工作。当然spring n内部还进行很多其他有趣的处理,有兴趣的同学可以详细看一下。

四、总结

综上所述,来总结下@RefreshScope 实现流程

  • 需要动态刷新的类标注@RefreshScope 注解
  • @RefreshScope 注解标注了@Scope 注解,并默认了ScopedProxyMode.TARGET_CLASS; 属性,此属性的功能就是在创建一个代理,在每次调用的时候都用它来调用GenericScope get 方法来获取对象
  • 如属性发生变更会调用 ContextRefresher refresh() -》RefreshScope refreshAll() 进行缓存清理方法调用,并发送刷新事件通知 -》 GenericScope 真正的 清理方法destroy() 实现清理缓存
  • 在下一次使用对象的时候,会调用GenericScope get(String name, ObjectFactory<?> objectFactory) 方法创建一个新的对象,并存入缓存中,此时新对象因为Spring 的装配机制就是新的属性了。

以上就是SpringCloud @RefreshScope刷新机制浅析的详细内容,更多关于SpringCloud @RefreshScope的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringCloud修改Feign日志记录级别过程浅析

    目录 前言 1. 介绍 2. 方式一 3. 方式二 前言 本次示例代码的文件结构如下图所示. 1. 介绍 Feign 允许我们自定义配置,下面是 Feign 可以修改的配置. 类型 作用 说明 feign.Logger.Level 修改日志级别 包含四种不同级别:NONE.BASIC.HEADERS.FULL feign.codec.Decoder 响应结果的解析器 HTTP 远程调用的结果做解析,例如解析 JSON 字符串反序列化成 Java 对象 feign.codec.Encoder 请求

  • Spring Cloud Ribbon 负载均衡使用策略示例详解

    目录 一.前言 二.什么是 Ribbon 2.1 ribbon简介 2.1.1  ribbon在负载均衡中的角色 2.2 客户端负载均衡 2.3 服务端负载均衡 2.4 常用负载均衡算法 2.4.1 随机算法 2.4.2 轮询算法 2.4.3 加权轮询算法 2.4.4 IP地址hash 2.4.5 最小链接数 三.Ribbon中负载均衡策略总探究 3.1 nacos中使用ribbon过程 3.1.1 添加配置类 3.1.2 接口层调用 3.2 Ribbon中负载均衡配置策略 3.2.1 IRul

  • Spring Cloud Gateway替代zuul作为API网关的方法

    目录 第一,pom文件 第二,项目结构 第三,项目代码和运行截图 运行效果图 参考文档: 本文简要介绍如何使用Spring Cloud Gateway 作为API 网关(不是使用zuul作为网关),关于Spring Cloud Gateway和zuul的性能比较本文不再赘述,基本可以肯定Spring Cloud Finchley版本的gateway比zuul 1.x系列的性能和功能整体要好. 特别提醒:Spring Cloud Finchley版本中,即使你引入了spring-cloud-sta

  • Spring Cloud Gateway远程命令执行漏洞分析(CVE-2022-22947)

    目录 漏洞描述 环境搭建 漏洞复现 声明:本文仅供学习参考,其中涉及的一切资源均来源于网络,请勿用于任何非法行为,否则您将自行承担相应后果,本人不承担任何法律及连带责任. 漏洞描述 使用Spring Cloud Gateway的应用程序在Actuator端点启用.公开和不安全的情况下容易受到代码注入的攻击.攻击者可以恶意创建允许在远程主机上执行任意远程执行的请求. 当攻击者可以访问actuator API时,就可以利用该漏洞执行任意命令. 影响范围 Spring Cloud Gateway <

  • Spring Cloud Alibaba 整合Nacos的详细使用教程

    目录 一.前言 二.常用服务注册中心介绍 2.1 dubbo服务注册示意图 2.2 常用注册中心对比 三.nacos介绍 3.1  什么是nacos 3.2 nacos 特点 3.3 nacos生态链地图 四.nacos部署 4.1 下载安装包 4.2 修改脚本启动模式 4.3  启动nacos 服务 五.Spring Cloud Alibaba 整合Nacos 5.1  Spring Cloud Alibaba版本选型 5.2  实验整合案例说明 5.3  整合完整过程 5.3.1 创建聚合工

  • springcloud整合openfeign使用实例详解

    目录 一.前言 二.微服务接口之间的调用问题 2.1 Httpclient 2.2 Okhttp 2.3 HttpURLConnection 2.4 RestTemplate 三.openfeign介绍 3.1 什么是 openfeign 3.2  openfeign优势 四.Spring Cloud Alibaba整合OpenFeign 4.1 前置准备 4.2  完整整合步骤 4.2.1 order模块添加feign依赖 4.2.2  添加feign接口类 4.2.3  调整调用的方法 4.

  • SpringCloud使用Feign实现远程调用流程详细介绍

    目录 前言 1. 导入依赖坐标 2. 开启Feign自动装配 3. 声明远程调用 4. 替代RestTemplate 5. 测试 前言 本次示例代码的文件结构如下图所示. 1. 导入依赖坐标 在 order-service 的 pom.xml 文件中导入 Feign 的依赖坐标. <!-- Feign远程调用客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifa

  • SpringCloud Hystrix熔断器使用方法介绍

    目录 Hystrix(hi si ju ke si)概述 Hystix 主要功能 隔离 Hystrix 降级 Hystrix降级-服务提供方 初始化程序和Fiegn程序一致 Hystrix降级-服务消费方 provider与Fiegin一致 Hystrix 熔断 Hystrix 熔断监控 Turbine聚合监控 搭建监控模块 修改被监控模块 启动测试 Hystrix(hi si ju ke si)概述 Hystix 是 Netflix 开源的一个延迟和容错库,用于隔离访问远程服务.第三方库,防止

  • 一文吃透Spring Cloud gateway自定义错误处理Handler

    目录 正文 AbstractErrorWebExceptionHandler isDisconnectedClientError方法 isDisconnectedClientErrorMessage方法: 小结 NestedExceptionUtils getRoutingFunction logError write 其他的方法 afterPropertiesSet renderDefaultErrorView renderErrorView DefaultErrorWebExceptionH

  • SpringCloud @RefreshScope刷新机制深入探究

    目录 梳理过程如下 @RefreshScope ScopedProxyMode RefreshAutoConfiguration NacosConfigService ClientWorker CacheData AbstractSharedListener NacosContextRefresher RefreshEventListener ContextRefresher EventPublishingRunListener RestartListener Java连接nacos后会定时心跳

  • SpringCloud Gateway路由组件详解

    目录 简介 核心概念 具体示例 GlobalFilter 简介   Gateway是SpringCloud Alibaba中的路由组件(前身是Zuul),作为浏览器端请求的统一入口.当项目采用微服务模式时,若包含了路由模块,浏览器端的请求都不会直接请求含有业务逻辑的各个业务模块,而是请求这个路由模块,然后再由它来转发到各个业务模块去. 核心概念   Gateway中的三个核心概念:路由.断言(Predicate).过滤器.   路由:由唯一id.目的url.断言和过滤组成   断言:即路由规则,

  • springcloud-gateway集成knife4j的示例详解

    目录 springcloud-gateway集成knife4j 环境信息 环境信息 准备工作 网关集成knife4j 编写配置类Knife4jGatewayConfig 测试验证 相关资料 springcloud-gateway集成knife4j 环境信息 环境信息 spring-boot:2.6.3 spring-cloud-alibaba:2021.0.1.0 knife4j-openapi2-spring-boot-starter:4.0.0 准备工作 各微服务&网关引入依赖 <dep

  • SpringCloud Gateway动态路由配置详解

    目录 路由 动态 路由模型实体类 动态路径配置 路由模型JSON数据 路由 gateway最主要的作用是,提供统一的入口,路由,鉴权,限流,熔断:这里的路由就是请求的转发,根据设定好的某些条件,比如断言,进行转发. 动态 动态的目的是让程序更加可以在运行的过程中兼容更多的业务场景. 涉及到两个服务,一个是门户服务(作用是提供给运营人员管理入口--包括:管理路由.绑定路由),一个是网关服务(gateway组件,为门户服务提供:查询路由信息.添加路由.删除路由.编辑路由接口). 路由模型实体类 /*

  • SpringCloud开启session共享并存储到Redis的实现

    目录 一.原架构 二.调整架构以及相应的代码 1.Redis和session的配置 2.增加配置类 3.应答过滤器增加session设置 4.增加控制台处理的过滤器ConsoleFilter 5.前端请求中增加(跨域时) 三.部署模式 1.同域 2.跨域 总结 备注:以下所有的gateway均指SpringCloud Gateway 一.原架构 前端<->gateway<->console后端 原来session是在console-access中维护的,当中间有了一层gateway

  • SpringCloud启动失败问题汇总

    目录 SpringCloud启动失败问题 Nacos配置读取失败 解决方案 总结 SpringCloud启动失败问题 Nacos配置读取失败 org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1        at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:218) ~

随机推荐