当前位置: 首页 > news >正文

Nacos自动刷新配置源码解析

文章目录

  • 前言
  • 一、@RefreshScope
    • 1.1、@RefreshScope的扫描
    • 1.2、代理类的创建
    • 1.3、GenericScope
    • 1.4、代理类的调用
  • 二、NacosConfigAutoConfiguration
  • 三、RefreshEventListener
    • 3.1、refreshEnvironment
    • 3.2、refreshAll
  • 总结 : @RefreshScope 注解的工作机制解析


前言

  在Nacos配置中心的使用中,当配置中心的相关配置信息发生变更,客户端(微服务)通常需要重新启动使变更生效。Nacos也提供了配置项热更新的功能,当配置中心中的配置被修改并发布后,标注了@RefreshScope 的 bean 会重新加载最新的配置。
  @RefreshScope 的作用范围是Spring的Bean,配置项热更新的原理是@RefreshScope 会让 bean 在配置变更时被销毁并重新创建

@RestController
@RefreshScope
public class TestController {@Value("${common.name}")private String name;@Value("${common.age}")private String age;@RequestMapping("/m1")public void test() {System.out.println(name + ":" + age);}
}

在这里插入图片描述
common.yml的初始值

在这里插入图片描述
访问http://localhost:8060/m1
在这里插入图片描述
修改配置信息

在这里插入图片描述
不重启应用,重新访问http://localhost:8060/m1,配置信息发生变更

一、@RefreshScope

  @RefreshScope是一个复合注解,其中包含了@Scope("refresh")注解:
在这里插入图片描述
  最关键的属性:proxyMode设置成了TARGET_CLASS,即当使用 @RefreshScope(或者其他 @Scope 注解)标记一个 Bean 时,Spring 将自动为该 Bean 创建一个代理对象,以便在作用域变动(如刷新)时动态替换真正的实例,外部依赖的其实是“代理对象”,当配置刷新时,RefreshScope 会销毁旧的真实对象,下次调用时重新构造(外部访问的依旧是代理对象)
在这里插入图片描述

1.1、@RefreshScope的扫描

  在Spring Boot 启动过程中,会调用容器的refresh方法。在invokeBeanFactoryPostProcessors这一步,会在ClassPathBeanDefinitionScannedoScan方法中,对目标路径下的所有类进行扫描。(如果没有指定,则扫描@SpringBootApplication注解所在的引导类的类路径)
  在doScan方法中,会去委托scopeMetadataResolver扫描@Scope注解:
在这里插入图片描述
  最终会获取注解中的 proxyMode,设置进ScopeMetadata供后续使用。
在这里插入图片描述
  然后会执行doScan中的AnnotationConfigUtils.applyScopedProxyMode:
在这里插入图片描述

1.2、代理类的创建

  在AnnotationConfigUtils.applyScopedProxyMode中,proxyTargetClass属性为true:
在这里插入图片描述
  最终调用到的是ScopedProxyUtils#createScopedProxy,在该方法中,首先会对bean的名称进行处理:(testController -> scopedTarget.testController )
在这里插入图片描述
  然后会创建一个ScopedProxyFactoryBean目标代理对象:
在这里插入图片描述
  那么ScopedProxyFactoryBean是在什么时候被触发的?
在这里插入图片描述
  ScopedProxyFactoryBean实现了BeanFactoryAware接口,会在bean生命周期的初始化这一步被调用,并执行setBeanFactory方法:
在这里插入图片描述
在这里插入图片描述
  在ScopedProxyFactoryBeansetBeanFactory方法中,完成了代理对象的创建,并且赋值给了proxy属性。

在这里插入图片描述

  上面的过程,是在Spring启动时完成的

1.3、GenericScope

  在1.2、代理类的创建 中,为什么有:

RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);

  这一行代码?原因在于,GenericScope在执行父类BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法时,需要对于目标类的Bean定义进行改造,主要是将目标类型替换为LockedScopedProxyFactoryBean
在这里插入图片描述

1.4、代理类的调用

  cglib在调用目标方法前,会先执行TargetSourcegetTarget方法:
在这里插入图片描述


  这里的TargetSource,是在spring启动过程中,bean生命周期的初始化这一步被调用,并执行setBeanFactory方法时被设置进去的:
在这里插入图片描述
在这里插入图片描述


  SimpleBeanTargetSourcegetTarget方法,是再次调用getBean,这次的bean名称,是scopedTarget.testController,并且它的作用域不再是单例,而是refresh
在这里插入图片描述
  单例bean会放在singletonObjects单例池中,refresh作用域的bean,也会放在一个类似于单例池的cache中:
在这里插入图片描述
  在GenericScopeget中,重点是getBean:
在这里插入图片描述
  如果获取不到,就会回调lambda表达式中的逻辑,创建bean:

在这里插入图片描述
在这里插入图片描述

  当执行目标方法时,实际会调用LockedScopedProxyFactoryBeaninvoke方法,该类是上一步中提到的ScopedProxyFactoryBean的子类:
在这里插入图片描述

在这里插入图片描述
流程图链接

二、NacosConfigAutoConfiguration

  在客户端引入的spring-cloud-starter-alibaba-nacos-configjar包中的spring.factories文件中,有一个关键的类:NacosConfigAutoConfiguration。它与Nacos配置中心的自动配置有关。其中与自动刷新配置相关的,是NacosContextRefresher
在这里插入图片描述
  它实现了ApplicationListener<ApplicationReadyEvent>接口:
在这里插入图片描述
  在Spring Boot启动过程中,就会触发监听ApplicationReadyEvent事件的类的onApplicationEvent方法:
在这里插入图片描述
  NacosContextRefresheronApplicationEvent方法:
在这里插入图片描述
  在registerNacosListenersForApplications方法中,会先判断是否支持自动刷新配置,然后会为每一个dataId绑定一个监听器:

在这里插入图片描述
  当这个dataId + group下的配置有变更时,就会触发上面的 innerReceive,发布一个RefreshEvent类型的事件。(这里只是将Listener进行注册,实际是在配置有变更的时候才会触发)
在这里插入图片描述
  上述过程是在Spring Boot启动过程中完成的

三、RefreshEventListener

  真正处理该事件的,是RefreshEventListener,那么这个监听器又是什么时候被加入Spring 容器的呢?
在这里插入图片描述


  在客户端spring-cloud-contextjar包中的spring.factories文件中,有一个RefreshAutoConfiguration,其中就将RefreshEventListener 注册成了bean。
在这里插入图片描述


  在RefreshEventListenerhandle方法中,又会去调用ContextRefresherrefresh方法:
在这里插入图片描述


  ContextRefresher也是RefreshAutoConfiguration中的一个bean:
在这里插入图片描述


  ContextRefresherrefresh方法:
在这里插入图片描述

3.1、refreshEnvironment

  在refreshEnvironment中,又做了三步操作:
在这里插入图片描述
  extract(this.context.getEnvironment().getPropertySources());会提取出除了SYSTEMJNDISERVLET的其他环境变量。(热更新前的参数值保存一份)
在这里插入图片描述
在这里插入图片描述
  addConfigFilesToEnvironment会模拟 Spring Boot 启动时加载配置文件的过程,把最新的配置加载出来,替换当前上下文的配置源(PropertySources),并完成一次“无感知的热刷新”。
  把已有配置源替换/插入进去,保留系统变量、JVM 参数、已有上下文等环境信息。相当于增量更新,只替换由配置文件加载的部分,别的都不动。
在这里插入图片描述
  changes(before,extract(this.context.getEnvironment().getPropertySources())).keySet();会找出新的参数值热更新前的参数值进行对比,找出变化的部分,然后发布一个EnvironmentChangeEvent环境变更事件。

3.2、refreshAll

  RefreshScoperefreshAll方法,首先会去把标注了@RefreshScope 的旧 Bean 销毁,然后发布一个RefreshScopeRefreshedEvent类型的事件:
在这里插入图片描述
  标注了@RefreshScope注解的类,可以同时实现ApplicationListener<RefreshScopeRefreshedEvent>。监听该事件。
  这里的destroy方法,实际调用的是父类GenericScope的,会将refresh作用域缓存池清空,然后销毁其中的所有bean。下一次访问目标类方法的时候,发现缓存池中没有,就会再次创建目标类,从而达到刷新的效果。
在这里插入图片描述

总结 : @RefreshScope 注解的工作机制解析

  当某个类(如 TestController)被标注为 @RefreshScope 时,Spring 应用在启动过程中会在应用上下文中注册两个相关的 Bean:

  • 名称为 testController 的 Bean,是由 ScopedProxyFactoryBean 创建的 CGLIB 动态代理对象,作用域为单例(singleton);
  • 名称为 scopedTarget.testController 的 Bean,是实际的目标类 TestController,其作用域为 refresh

  代理对象的主要作用是拦截对目标类的访问,在方法调用前,通过内部的 TargetSource 获取当前上下文中最新的目标实例。当目标实例因配置变更而被刷新时,代理对象能够自动指向最新的实例,从而实现运行时的动态刷新。

  在 Spring Boot 应用启动时,若启用了配置自动刷新机制,Spring Cloud 会为每个 Nacos 的 dataId 注册一个配置变更监听器,并通过 NacosContextRefresher 将变更事件传递到 Spring 环境。容器中也会注册一个 RefreshEventListener,监听并响应配置变更事件。

  配置刷新由 ContextRefresherrefresh() 方法驱动,其核心流程如下:

  1. 提取环境变量:排除系统环境(如 SYSTEMJNDISERVLET)后,提取用户自定义的配置项,并缓存刷新前的配置值;
  2. 加载最新配置:模拟 Spring Boot 启动过程,重新加载配置源(PropertySources),更新环境上下文;
  3. 检测参数变更:对比新旧配置,识别变化项,并发布 EnvironmentChangeEvent 环境变更事件;
  4. 刷新作用域 Bean:调用 refreshAll() 方法,清空 refresh 作用域的缓存池并销毁其中的 Bean。下一次访问该 Bean 时,将自动重新创建实例,从而完成动态刷新。

相关文章:

  • Ubuntu22.04新版本谷歌无法使用搜狗输入法/中文不显示
  • 组织级项目管理OPM
  • Spark 集群搭建:Standalone 模式详解
  • 天梯赛补题
  • Kafka 详解
  • Qt creator 16.0.1 语言家失效解决方法
  • 使用 VSCode 编写 Markdown 文件
  • Vscode已经打开的python项目,如何使用已经建立的虚拟环境
  • 局部最小实验--用最小成本确保方向正确
  • 信息学奥赛一本通 1505:【例 2】双调路径 | 洛谷 P5530 [BalticOI 2002] 双调路径
  • 03-谷粒商城笔记
  • MongoDB(docker版)备份还原
  • 八大排序——选择排序/堆排序
  • Android APP 爬虫操作
  • 海外产能达产,威尔高一季度营收利润双双大增
  • 【k8s】docker、k8s、虚拟机的区别以及使用场景
  • shell脚本1
  • SwiftUI 常用控件简介
  • Hi3518E官方录像例程源码流程分析(五)
  • PNG透明免抠设计素材大全26000+
  • 神二十发射取得圆满成功
  • 俄外长拉夫罗夫将出席金砖国家外长会
  • “80后”阿伯丁大学法学硕士朱江已任四川泸州市副市长
  • 生态环境部:我国核电规模全球第一,总体安全可控
  • IMF将今年美国经济增长预期下调0.9个百分点至1.8%
  • 张又侠董军分别与印尼国防部长会见会谈