一文带你理解@RefreshScope注解实现动态刷新原理

简介: 一文带你理解@RefreshScope注解实现动态刷新原理

概述


RefeshScope这个注解想必大家都用过,在微服务配置中心的场景下经常出现,他可以用来刷新Bean中的属性配置,那大家对他的实现原理了解吗?它为什么可以做到动态刷新呢?


注解的作用


@RefreshScope注解是Spring Cloud中的一个注解,用来实现Bean中属性的动态刷新。

/**
 * Convenience annotation to put a <code>@Bean</code> definition in
 * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}.
 * Beans annotated this way can be refreshed at runtime and any components that are using
 * them will get a new instance on the next method call, fully initialized and injected
 * with all dependencies.
 *
 * @author Dave Syer
 *
 */
@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注解使用,@Scope用来比较Spring Bean的作用域,具体使用参考相关文章。
  • 注解的属性proxyMode默认使用TARGET_CLASS作为代理。


实例


  1. controller中添加@RefreshScope

1671172936000.jpg

  1. nacos配置中心中配置

1671172957046.jpg

  1. 验证, 修改配置中心后,可以不重启动,刷新配置

1671172965085.jpg

1671172971518.jpg

1671172977927.jpg

  1. 去掉@RefreshScope 就不会自动刷新。

代码地址:github.com/alvinlkk/aw…


原理解析


为了实现动态刷新配置,主要就是想办法达成以下两个核心目标:

  1. 让Spring容器重新加载Environment环境配置变量
  2. Spring Bean重新创建生成

@RefreshScope主要就是基于@Scope注解的作用域代理的基础上进行扩展实现的,加了@RefreshScope注解的类,在被Bean工厂创建后会加入自己的refresh scope 这个Bean缓存中,后续会优先从Bean缓存中获取,当配置中心发生了变更,会把变更的配置更新到spring容器的Environment中,并且同事bean缓存就会被清空,从而就会从bean工厂中创建bean实例了,而这次创建bean实例的时候就会继续经历这个bean的生命周期,使得@Value属性值能够从Environment中获取到最新的属性值,这样整个过程就达到了动态刷新配置的效果。

如果对Scope注解不清楚的可以阅读这篇文章:【Spring注解必知必会】@Scope注解源码解析

image.png

获取RefreshScope注解的Bean


1671173034553.jpg

通过打上断点查看堆栈可知:

  1. 因为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);
  }
}

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


RefeshScope Bean的创建


上面的在getBean的时候依赖到RefreshScope这个Bean,那么这个Bean是在什么时候加入到Spring Bean中的呢?答案就是RefreshAutoConfiguration

1671173053299.jpg


配置中心刷新后刷新Bean缓存


1671173062696.jpg

  1. 配置中心发生变化后,会收到一个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;
  }
  1. 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();
  }


总结


上面是这个RefreshScope实现动态刷新大致的原理,其中里面还有很多细节,可能需要留给大家自己debug去深入理解。

目录
相关文章
|
存储 缓存 Java
一文带你从零到一深入透析 @RefreshScope 结合 Nacos 动态刷新源码(下)
一文带你从零到一深入透析 @RefreshScope 结合 Nacos 动态刷新源码(下)
1017 0
|
11月前
|
Java 微服务 Spring
手把手教你实现@RefreshScope注解
【10月更文挑战第16天】 在Spring Cloud中,@RefreshScope注解被广泛用于动态刷新配置。当我们修改了配置文件中的值,并且希望这些更改在不重启应用的情况下立即生效时,这个注解就显得尤为重要。本文将带你一步步实现一个简单的@RefreshScope功能。
691 5
|
算法 Java 关系型数据库
Springboot yml配置参数加密 ,jasypt自定义解密器(拓展篇)
Springboot yml配置参数加密 ,jasypt自定义解密器(拓展篇)
1233 0
Springboot yml配置参数加密 ,jasypt自定义解密器(拓展篇)
|
9月前
|
Docker 容器
将本地的应用程序打包成Docker镜像
将本地的应用程序打包成Docker镜像
|
11月前
|
Java 云计算 微服务
手写@RefreshScope,很简单嘛!
【10月更文挑战第8天】 在微服务架构和云计算时代,动态配置管理变得越来越重要。Spring Cloud提供了@RefreshScope注解,允许我们在不重启应用的情况下,动态刷新配置。但你有没有想过,这个注解是如何实现的呢?本文将带你一起手写一个简化版的@RefreshScope,一探究竟!
227 7
|
10月前
|
安全 Java API
深入探索Java网络编程中的HttpURLConnection:从基础到进阶
本文介绍了Java网络编程中HttpURLConnection的高级特性,包括灵活使用不同HTTP方法、处理重定向、管理Cookie、优化安全性以及处理大文件上传和下载。通过解答五个常见问题,帮助开发者提升网络编程的效率和安全性。
454 9
|
Java Spring
spring restTemplate 进行http请求的工具类封装
spring restTemplate 进行http请求的工具类封装
525 3
|
缓存 Java 微服务
Spring Cloud中@RefreshScope实现动态刷新的原理
Spring Cloud中@RefreshScope实现动态刷新的原理
|
XML JSON Java
spring,springBoot配置类型转化器Converter以及FastJsonHttpMessageConverter,StringHttpMessageConverter 使用
spring,springBoot配置类型转化器Converter以及FastJsonHttpMessageConverter,StringHttpMessageConverter 使用
1294 1
|
Prometheus 监控 Cloud Native
Java 服务挂掉,服务器异常宕机问题排查
Java 服务挂掉,服务器异常宕机问题排查
2223 1