SpringCloud @RefreshScope动态刷新配置原理浅析

文章目录

  • 前言
  • 一、demo
  • 二、RefreshScope动态刷新配置机制整体介绍
  • 三、Spring容器注册`@RefreshScope`注解修饰bean流程分析
  • 三、Spring容器注册getBean流程简单介绍
  • 四、spring-cloud-commons 引入RefreshScope类
  • 五、spring-cloud-commons 引入RefreshEventListener监听类
  • 六、spring-cloud-alibaba-nacos-config 引入NacosContextRefresher
  • 七、SpringBoot启动完成后发布ApplicationReadyEvent事件


前言

@RefreshScope是SpringCloud引入的注解,这个注解可用做到动态刷新配置的功能,这里我们用来分析一些背后的逻辑


一、demo

启动nacos

SpringCloud @RefreshScope动态刷新配置原理浅析_第1张图片

startup.cmd -m standalone 

本地启动Nacos

启动后访问地址: http://localhost:8848/nacos/index.html#/login

SpringCloud @RefreshScope动态刷新配置原理浅析_第2张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第3张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第4张图片

启动项目打印出来的age是33
在nacos管理台改下配置再调用看看

SpringCloud @RefreshScope动态刷新配置原理浅析_第5张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第6张图片

这个请求调用到上面的SampleController ,可用看到被 @RefreshScope注解修饰了,里面的变量会跟着动态刷新(从Spring Environment中拿到数据)

二、RefreshScope动态刷新配置机制整体介绍

上面的例子可用看到,一个@RefreshScope注解就能实现动态刷新的功能,这也体现出来Spring的强大,虽然用起来简单,但是这里背后涉及到的知识点、逻辑还是蛮多的(涉及到Spring SpringCloud spring-cloud-alibaba nacos)这里简单概括下,后面再具体分析

SpringCloud @RefreshScope动态刷新配置原理浅析_第7张图片

三、Spring容器注册@RefreshScope注解修饰bean流程分析

对于demo中的SampleController,这个类是要被注入到容器里面,这是Spring干的事,我们来具体分析下
首先我们要知道ClassPathBeanDefinitionScanner 这个类,doScan方法扫描指定路径下的类注册到Spring容器,在扫描接收后会判断筛选出来的类是否有Scope注解,是否重新生成BeanDefinition

SpringCloud @RefreshScope动态刷新配置原理浅析_第8张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第9张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第10张图片

也就是Spring容器扫描到@RefreshScope注解修饰的类,注册到容器的bean对应的beanClass是ScopedProxyFactoryBean(这是一个工厂bean 获取这个bean实例的时候拿到是生成的代理对象)另外我们注意下这个bean 的scope属性,默认是SCOPE_DEFAULT

SpringCloud @RefreshScope动态刷新配置原理浅析_第11张图片

@RefreshScope注解生成的bean这个属性是 refresh

SpringCloud @RefreshScope动态刷新配置原理浅析_第12张图片

这个scope属性取的是scope注解的value值,RefreshScope注解对应的是refresh

SpringCloud @RefreshScope动态刷新配置原理浅析_第13张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第14张图片

这里要提到这个Scope属性,是因为后面getBean的流程跟这个有关

三、Spring容器注册getBean流程简单介绍

在AbstractBeanFactory中doGetBean定义了获取bean的逻辑,这里面根据BeanDefinition的Scope属性有三个分支
分别是Scope属性为 singleton或者为空字符串,Scope属性为prototype,再就是这两种情况之外的value
上面说到RefreshScope注解修饰的类注册到容器对应的scope属性是refresh,这就是第三种情况

SpringCloud @RefreshScope动态刷新配置原理浅析_第15张图片

这种情况下根据scope的值到spring容器中拿到对应的Scope类,通过Scope类的get方法获取对象

	private final Map<String, Scope> scopes = new LinkedHashMap<>(8);

也是在AbstractBeanFactory这里类里面定义了一个Map来放Scope类,那么问题又来了,这个Map里面的数据(Scope类)从哪里来呢,这就是后面要讲的了

四、spring-cloud-commons 引入RefreshScope类

spring-cloud-commons 是spring-cloud项目都会引入的组件(这个demo用到了spring-cloud-alibaba)

SpringCloud @RefreshScope动态刷新配置原理浅析_第16张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第17张图片
SpringCloud项目实际也是扩展了SpringBoot,我们知道SpringBoot启动后会读取spring.factories文件,解析EnableAutoConfiguration 对应配置类注册一些bean到Spring容器,这里就注册了一个RefreshScopebean,看这个类名就知道跟上面提到 RefreshScope注解修饰的bean有关系(是的都叫RefreshScope这个名字,一个是注解,一个是类,类是用来获取对应对象的工具类)

我们先看看RefreshScope这个类的继承体系

SpringCloud @RefreshScope动态刷新配置原理浅析_第18张图片

往上继承了GenericScope这个类,其实主要干活的就是这个类,是一个通用的Scope处理类

这个类实现了BeanFactoryPostProcessor接口,在容器初始化前调用postProcessBeanFactory方法,把自己注册到Spring存Scope对象的Map中,这个name吗由子类提供(GenericScope )

SpringCloud @RefreshScope动态刷新配置原理浅析_第19张图片

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
		beanFactory.registerScope(this.name, this);
		setSerializationId(beanFactory);
	}

所以回到前面的问题,如果getBean对应的bean Scope既不是单例,也不是原型,要从Spring缓存Scope的Map中找到对应处理的Scope类,并调用get方法创建实例,spring-cloud-commons 引入的RefreshScope类处理的就是Scope属性为refresh的bean

上面的GenericScope还实现了BeanDefinitionRegistryPostProcessor这个接口,这个也是在Spring容器启动refresh流程中执行,

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		for (String name : registry.getBeanDefinitionNames()) {
			BeanDefinition definition = registry.getBeanDefinition(name);
			if (definition instanceof RootBeanDefinition) {
				RootBeanDefinition root = (RootBeanDefinition) definition;
				if (root.getDecoratedDefinition() != null && root.hasBeanClass()
						&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
					if (getName().equals(root.getDecoratedDefinition().getBeanDefinition().getScope())) {
						root.setBeanClass(LockedScopedProxyFactoryBean.class);
						root.getConstructorArgumentValues().addGenericArgumentValue(this);
						// surprising that a scoped proxy bean definition is not already
						// marked as synthetic?
						root.setSynthetic(true);
					}
				}
			}
		}
	}

这里替换了下RefreshScope注解修饰类在容器中Bean 的beanClass,由原来的ScopedProxyFactoryBean替换成LockedScopedProxyFactoryBean

SpringCloud @RefreshScope动态刷新配置原理浅析_第20张图片

LockedScopedProxyFactoryBean 是 ScopedProxyFactoryBean 子类,改下这个类就是为了扩展一些功能,ScopedProxyFactoryBean 是工厂bean,在setBeanFactory方法里面生成了getObect所需的代理类,在LockedScopedProxyFactoryBean 子类中往这个代理类中加了一个过滤器,在访问业务方法前加锁

五、spring-cloud-commons 引入RefreshEventListener监听类

同样是在 RefreshAutoConfiguration配置文件类,向Spring容器注册了RefreshEventListener监听器类

    @Bean
	public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
		return new RefreshEventListener(contextRefresher);
	}

SpringCloud @RefreshScope动态刷新配置原理浅析_第21张图片
这个类监听到了容器中的RefreshEvent事件后,调用handle方法

public void handle(RefreshEvent event) {
		if (this.ready.get()) { // don't handle events before app is ready
			log.debug("Event received " + event.getEventDesc());
			Set<String> keys = this.refresh.refresh();
			log.info("Refresh keys changed: " + keys);
		}
	}

调用 ContextRefresher refresh方法

SpringCloud @RefreshScope动态刷新配置原理浅析_第22张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第23张图片

清空了GenericScope get方法创建的缓存实例

我们回头再看看getBean的流程

SpringCloud @RefreshScope动态刷新配置原理浅析_第24张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第25张图片

将表达式传给GenericScope get方法获取实例,如果是第一次调用,肯定要走一遍bean创建流程,
如果第二次调用put方法拿到的是第一次调用时缓存的对象,后面value.getBean获取对象也不用再走一遍bean的创建流程

当接收到RefreshEvent事件后清空调cache里面的数据,再有请求进来,拿到的BeanLifecycleWrapper 里面都是空的调用value.getBean需要再走一遍bean创建的流程,在这之前 还调用refreshEnvironment刷新了Spring的Environment信息,所有再次创建bean bean里面绑定的属性值就是最新的啦

这里RefreshEventListener 监听RefreshEvent事件做到了动态刷新容器中带有RefreshScope注解的bean

那么接下来我们就要了解下在哪里触发这个事件了

六、spring-cloud-alibaba-nacos-config 引入NacosContextRefresher

SpringCloud @RefreshScope动态刷新配置原理浅析_第26张图片

NacosContextRefresher这是一个监听器,在NacosConfigAutoConfiguration配置类中引入,监听ApplicationReadyEvent事件

SpringCloud @RefreshScope动态刷新配置原理浅析_第27张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第28张图片

在nacos配置发生变化后就发布RefreshEvent事件,RefreshEventListener 监听到这个事件更新Spring 的Environment

七、SpringBoot启动完成后发布ApplicationReadyEvent事件

这个ApplicationReadyEvent方法在SpringBoot启动过程中最后调用(SpringApplication run方法最后调用),这时候Spring容器也创建完成了

SpringCloud @RefreshScope动态刷新配置原理浅析_第29张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第30张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第31张图片

SpringCloud @RefreshScope动态刷新配置原理浅析_第32张图片
发布ApplicationReadyEvent事件后,NacosContextRefresher监听这个事件 再发送RefreshEvent事件到RefreshEventListener监听器,这样就完成了RefreshScope动态刷新的整个流程

你可能感兴趣的:(SpringCloud,spring,cloud,spring,后端)