SpringCloud @RefreshScope注解源码层面深入分析

作者:程序员李哈 时间:2023-06-14 03:17:27 

写在前面

最近在研究Spring Cloud和Spring Cloud Alibaba源码,在看到Nacos的配置中心的时候,有注意到自动刷新配置的玩法,底层实现依靠@RefreshScope注解。那么为什么要写这篇文章呢?笔者认为@RefreshScope注解源码实现跨度特别大,从Spring Cloud Alibaba 到Spring Cloud 到 Spring Boot 再到Spring,笔者认为能够理解它的源码实现的话对Spring全家桶的理解又能上升一个档次~

版本如果下:

Spring:5.3.23
Spring Boot:2.6.3
Spring Cloud:3.1.4
Spring Cloud Alibaba:2021.0.4.0
Nacos:2.0.4

先会用,再深入源码,所以我们需要从案例出发。

@RestController
@RefreshScope
public class ConsumerController {
   @Value("${consumer.value:moren}")
   private String value;
   @RequestMapping("/consumer")
   public String consumer(){
       return value;
   }
}

SpringCloud @RefreshScope注解源码层面深入分析

SpringCloud @RefreshScope注解源码层面深入分析

能够正常使用Nacos服务端的配置数据,所以,我们发现在类上存在@RefreshScope注解,所以我们的重心看往@RefreshScope注解。

@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注解,并且赋予了"refresh". 此时,从Spring惯用玩法,我们需要找到Spring何时解析的@Scope注解,何时解析的@RefreshScope注解。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {  
               // 解析当前类上是否存在@scope注解
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
…………
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                   // 生成Scope代理类
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
}
return metadata;
}
protected Class<? extends Annotation> scopeAnnotationType = Scope.class;

上面代码,是解析@ComponentScan时,扫描指定路径包的@Component注解类,而我们的@RestController也是一个@Component,并且我们的@RestController类上还有@RefreshScope注解,而@RefreshScope注解又存在@Scope注解。所以,接下来我们分析如何做Scope的代理。

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
       // 拿到原类的名字,和对应的BeanDefinition
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
       // 手动创建Scope代理类的BeanDefinition
       // 设置BeanDefinition的BeanClass为ScopedProxyFactoryBean
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
       …………
       // 将代理类设置为自动注入,并且优先级最高。        
       proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
// 将原类设置为不能自动注入
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// 将原类的BeanDefinition注册到工厂中
registry.registerBeanDefinition(targetBeanName, targetDefinition);
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
public static String getTargetBeanName(String originalBeanName) {
return TARGET_NAME_PREFIX + originalBeanName;
}
private static final String TARGET_NAME_PREFIX = "scopedTarget.";

这里就是非常关键的部分, 有没有感觉是在"偷天换日"。手动创建BeanDefintion,把ScopedProxyFactoryBean作为BeanClass,并且把原类的name作为手动创建BeanDefintion的name。把原类的BeanDefinition的name加上前缀scopedTarget. 相信Spring底子好的读者很容易看明白。下面是改变的流程图。

SpringCloud @RefreshScope注解源码层面深入分析

那么,说了这么多的意义在哪里呢?我们的主题不是动态刷新么,怎么连注册中心的影子都没见到?年轻人,不要着急,文章开头就说了本篇文章跨度很大~

此时,我们需要从Spring Cloud规范包入手,相信各位读者知道,Spring-Cloud-Commons和Spring-Cloud-Context 这两个Spring Cloud规范包。从他们的spring.factories自动装配包可以得知以下信息。

SpringCloud @RefreshScope注解源码层面深入分析

@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
return new RefreshScope();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapEnabled
public LegacyContextRefresher legacyContextRefresher(ConfigurableApplicationContext context, RefreshScope scope,
RefreshProperties properties) {
return new LegacyContextRefresher(context, scope, properties);
}
@Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
return new RefreshEventListener(contextRefresher);
}

从spring.factroies自动装配规范文件中我们能看到RefreshAutoConfiguration类,从类名也能获取到很多信息。再从其中的@Bean中可以看到RefreshScope 、LegacyContextRefresher 、RefreshEventListener三个类。那么下面从笔者的解释和源码深入理解这三个类~!

  • RefreshScope:扩展@Scope注解,并且CRUD名字为refresh的@Scope注解类,之前介绍的@RefreshScope注解中存在value为refresh的@Scope注解。

  • LegacyContextRefresher:用来刷新Environment。

  • RefreshEventListener:用来监听ApplicationReadyEvent和RefreshEvent事件。

public class RefreshScope extends GenericScope
implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered
// RefreshScope的父类
// 实现了BeanFactoryPostProcessor扩展接口
public class GenericScope
implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
   // 把当前Scope注册到BeanFacotry中。
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
@Override
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);
root.setSynthetic(true);
}
}
}
}
}

这里注意到RefreshScope自动注入的类,这里需要区分@RefreshScope注解和RefreshScope类。它的父类GenericScope实现了BeanFactroyPostProcessor。在postProcessBeanDefinitionRegistry回掉中可以清楚的看到再一次上演了"偷天换日",把ScopedProxyFactoryBean换成了LockedScopedProxyFactoryBean。并且在postProcessBeanFactory回掉中把RefreshScope作为Scope注册到BeanFacotry工厂中。

// SmartApplicationListener子类 *
public class RefreshEventListener implements SmartApplicationListener
// 事件回掉。
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
handle((ApplicationReadyEvent) event);
}
   // 一定要注意到RefreshEvent事件,非常非常非常重要!!!
   // 后续我们只需要找到哪里发出的这个事件即可。
else if (event instanceof RefreshEvent) {
handle((RefreshEvent) event);
}
}
public void handle(RefreshEvent event) {
if (this.ready.get()) {
Set<String> keys = this.refresh.refresh();
}
}
public synchronized Set<String> refresh() {
   // 刷新Environment上下文。而我们知道配置数据是放在Environment中的。
Set<String> keys = refreshEnvironment();
   // 调用scope的refreshAll,从方法就可以知道,要刷新所有的数据
this.scope.refreshAll();
return keys;
}
  • RefreshEventListener作为SmartApplicationListener的子类实现onApplicationEvent方法

  • 监听ApplicationReadyEvent和RefreshEvent事件

  • 调用ContextRefresh类(LegacyContextRefresher)的refresh方法

  • refreshEnvironment方法刷新Environment,返回发生改变的配置数据

  • 调用RefreshScope类的refreshAll方法,把整个RefreshScope中存放的Bean给destroy。

public synchronized Set<String> refreshEnvironment() {
   // 拿到更新前的Environment
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
   // 更新Environment。
updateEnvironment();
   // 对比更新后和更新前,返回发生变化的数据。
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
@Override
protected void updateEnvironment() {
addConfigFilesToEnvironment();
}
ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());
…………
// 创建一个Spring Boot的启动器,目的:为了创建出Spring的上下文(这样整个上下文刷新就可以得到最新的environment)。
// 这里要注意,刷新Spring上下文的environment是手动放入的,
// 也即重新刷新Spring上下文的环境变量会加载到手动创建的environment中
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).bannerMode(Banner.Mode.OFF)
.web(WebApplicationType.NONE).environment(environment);
builder.application().setListeners(
Arrays.asList(new BootstrapApplicationListener(), new BootstrapConfigFileApplicationListener()));
capture = builder.run();
MutablePropertySources target = getContext().getEnvironment().getPropertySources();
String targetName = null;
// 把再次刷新Spring上下文environment得到的数据赋值到原有的environment环境变量中(这不就完成了配置数据的刷新么)
for (PropertySource<?> source : environment.getPropertySources()) {
if (!this.standardSources.contains(name)) {
if (target.contains(name)) {
target.replace(name, source);
}
else {
if (targetName != null) {
target.addAfter(targetName, source);
// update targetName to preserve ordering
targetName = name;
}
else {
// targetName was null so we are at the start of the list
target.addFirst(source);
targetName = name;
}
}
}
}
}
// finally中把临时创建出的Application上下文给关闭。
finally {
ConfigurableApplicationContext closeable = capture;
while (closeable != null) {
closeable.close();
if (closeable.getParent() instanceof ConfigurableApplicationContext) {
closeable = (ConfigurableApplicationContext) closeable.getParent();
}
else {
break;
}
}
}
return capture;
}

这里就是重点所在。一言以蔽之:手动创建Environment对象,然后重新走一遍上下文刷新,这样可以得到最新的配置文件,然后把手动创建Environment对象赋值给当前旧的上下文,这样就完成了动态刷新配置。详细流程如下:

  • 创建出Environment对象。

  • 创建SpringApplicationBuilder对象,也即Spring Boot的启动器(手动传入创建出的Environment对象,这样刷新上下文的时候使用的是这里创建的Environment对象)

  • 调用SpringApplication的run方法,刷新Spring Boot和Spring上下文(刷新完成后返回Spring上下文)。

  • 把刷新Spring Boot和Spring上下文得到的Environment对象的属性拷贝到当前Spring上下文中

  • close掉刷新完成后返回Spring上下文的。

public void refreshAll() {
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 {
wrapper.destroy();
}
finally {
lock.unlock();
}
}
catch (RuntimeException e) {
errors.add(e);
}
}
}

这里就是把RefreshScope中所有的缓存的给clear,并且回掉Bean的destroy方法。这里我必须再次强调RefreshScope的作用,就是CRUD 类上标有value为"refresh"的@Scope注解(@RefreshScope不就是么,所以我们的ConsumerController这个Bean就是交给RefreshScope管理,可能到这里笔者有点懵逼,为什么ConsumerController交给RefreshScope管理?他不是Spring的Bean么,不是要进入 * 缓存中么???那么下面就是为了解答这个问题。)

我们看到getBean的doGetBean方法创建Bean的流程中。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
   // 创建singleton的bean
if (mbd.isSingleton()) {
       …………
}
   // 创建Prototype的bean
else if (mbd.isPrototype()) {
…………
}
   // 创建其他Scope作用域的bean
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
       // 根据name拿到对应的Scope。
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
           // 调用scope的get方法,所以对应的scope可以缓存。
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);
}
}
}

在else代码块中会去处理不同Scope作用域的Bean,而在RefreshScope的父类GenericScope中的postProcessBeanFactory回掉方法中(前面有介绍)会把RefreshScope作为一个Scope注册到BeanFactory中(所以上文的refreshAll方法把RefreshScope的缓存全部clear掉了,下次就会去createBean,就会重新走一遍Spring创建Bean的过程,而环境变量已经更改了,@Value注解的注入就会注入到新的环境变量中的配置数据)。

这里把RefreshScope的缓存全部clear掉了,那么总要有一个地方每次都来拿一遍数据(这样缓存在就拿缓存的,缓存不在(缓存不在就代表被clear了,而clear掉了代表有地方发生了RefreshEvent事件,执行了refreshAll方法和refreshEnvironment,缓存被清除了,环境变量被更改了)就重新createBean,拿到最新的环境变量)

此时,我们是不是忘了,我们的ConsumerController被代理了呢?上文介绍RefreshScope的父类GenericScope中postProcessBeanDefinitionRegistry方法注册了LockedScopedProxyFactoryBean。

public static class LockedScopedProxyFactoryBean<S extends GenericScope> extends ScopedProxyFactoryBean
implements MethodInterceptor {
   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
   // 反射的Method
       Method method = invocation.getMethod();
   …………
   try {
   if (proxy instanceof Advised) {
   Advised advised = (Advised) proxy;
   ReflectionUtils.makeAccessible(method);
   // advised.getTargetSource().getTarget()方法会去getBean
   return ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(),
invocation.getArguments());
   }
   return invocation.proceed();
   }
   }
}
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
@Override
public Object getTarget() throws Exception {
// 看到熟悉的getBean方法了。
return getBeanFactory().getBean(getTargetBeanName());
}
}

LockedScopedProxyFactoryBean实现了MethodInterceptor接口,所以只要调用ConsumerController类中的方法就会走到LockedScopedProxyFactoryBean的invoke方法。恰好在invoke方法中会去调用BeanFactory的getBean方法。而getBean再到doGetBean,再到上面介绍的else代码块中,就走到RefreshScope类中的get方法了。

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
   // 拿缓存,如果缓存中没有就创建
BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
       // 拿缓存,如果缓存中没有就回掉createBean方法。
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}

看到get方法其实就恍然大悟了,因为在LockedScopedProxyFactoryBean的invoke方法中会调用getBean,getBean调用doGetBean中会调用scope.get方法,而在get方法中会去拿缓存,如果没有缓存就会创建一个新的,而新的就会去执行createBean方法创建一个新的Bean出来。恰好在这之前缓存已经被清除了,环境变量更新了。最终createBean方法创建的时候@Value注入的就是最新的环境变量中的配置数据。

所以,RefreshScope类+@RefreshScope注解控制了Bean的创建,RefreshEvent事件控制了缓存的clear和环境变量的更新。但是我们似乎还没有闭环RefreshEvent事件在哪里发出的。

public class NacosContextRefresher
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
private void registerNacosListener(final String groupKey, final String dataKey) {
// 一个group、dataid对应一组事件。
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
// 创建一个Nacos事件 *
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
// 这里发送了RefreshEvent事件。
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
}
});
try {
// 添加Nacos的内部事件
configService.addListener(dataKey, groupKey, listener);
}
}
}

对于Nacos的配置中心代码不过细讲,我们能够知道RefreshEvent事件是从Nacos内部发出的即可

来源:https://blog.csdn.net/qq_43799161/article/details/129954153

标签:SpringCloud,@RefreshScope,注解
0
投稿

猜你喜欢

  • Android Handler源码深入探究

    2023-02-17 11:54:53
  • Java中的泛型方法详解及简单实例

    2023-09-11 13:28:05
  • SpringBoot中如何对actuator进行关闭

    2022-11-30 01:56:37
  • CentOS安装jdk的三种方法

    2022-01-13 06:24:41
  • C#实现自定义双击事件

    2023-05-01 16:31:57
  • springBoot整合rabbitMQ的方法详解

    2022-08-19 02:28:33
  • 如何获取Android设备挂载的所有存储器

    2023-08-16 12:44:41
  • Java多线程下载网图的完整案例

    2022-10-17 23:59:02
  • Dwr3.0纯注解(纯Java Code配置)配置与应用浅析一之零配置文件化

    2022-06-05 05:20:17
  • 替换so文件来动态替换Flutter代码实现详解

    2023-06-23 16:24:06
  • Java查找并高亮PDF文本过程解析

    2022-02-17 07:20:32
  • OKhttp拦截器实现实践环节源码解析

    2023-04-27 05:00:21
  • Android中使用findViewByMe提升组件查找效率

    2021-08-12 08:30:13
  • c#窗体传值用法实例详解

    2022-04-04 03:44:15
  • springboot验证码生成以及验证功能举例详解

    2023-11-19 03:14:23
  • 解决springcloud中Feign导入依赖为unknow的情况

    2022-02-03 14:05:45
  • Intellij IDEA 配置Subversion插件实现步骤详解

    2023-11-03 10:09:04
  • C# 打开蓝牙设置界面的两种方法

    2021-12-22 04:51:43
  • c#使用linq把多列的List转化为只有指定列的List

    2022-07-04 12:00:31
  • 在Flutter中制作翻转卡片动画的完整实例代码

    2023-06-23 23:31:21
  • asp之家 软件编程 m.aspxhome.com