Spring Cloud中@RefreshScope实现动态刷新的原理

简介: Spring Cloud中@RefreshScope实现动态刷新的原理

一、@RefreshScope注解的作用

首先,让我们来了解一下@RefreshScope注解的作用。在Spring Cloud中,@RefreshScope是一个特殊的scope注解,它用于标记那些需要动态刷新的Bean。当一个Bean被@RefreshScope注解时,

Spring容器会为这个Bean创建一个特殊的scope,称为refresh scope。这意味着,当配置发生变化时,Spring容器能够重新创建这个Bean的实例,并使用新的配置。

@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作为代理。
  • @RefreshScope本质上是一个特殊的作用域,它通过继承GenericScope类并重写相关方法来实现配置刷新的逻辑。
  • @Scope注解的作用是指定RefreshScope作为一个新的作用域类型。具体来说,@RefreshScope注解内部使用了@Scope注解,并将其value属性设置为"refresh",这表示定义了一个新的作用域名为refresh。当Spring容器遇到@RefreshScope注解时,它会知道这是一个特殊的作用域,需要按照RefreshScope的逻辑来处理Bean的创建、缓存和销毁。

@Scope注解用于指定由Spring IoC容器管理的Bean的作用域。作用域决定了Bean的生命周期、创建时机以及存储方式。常见的Spring作用域包括singleton(单例)、prototype(原型)、request(请求)、session(会话)等。

二、@RefreshScope实现动态刷新的流程

在Spring Cloud中,@RefreshScope实现动态刷新的流程可以总结为以下几个步骤:

  1. 定义@RefreshScope注解: 开发者在需要动态刷新的Bean上使用@RefreshScope注解。这个注解内部使用了@Scope注解,并将其值设置为"refresh",定义了一个新的作用域名为refresh。
  2. Spring容器解析@RefreshScope: 当Spring容器启动时,它会解析所有的Bean定义,并遇到@RefreshScope注解时,Spring容器会知道这是一个特殊的作用域。它使用RefreshScope类(继承自GenericScope)来处理这些Bean的生命周期。
  3. Bean的创建和缓存: 当应用首次请求一个被@RefreshScope标记的Bean时,Spring容器会调用RefreshScope的get方法来创建Bean的实例。创建完成后,这个Bean实例会被缓存在RefreshScope中,以便后续快速获取。
  4. 配置更改: 在运行时,如果外部配置源中的配置发生了更改(比如通过Spring Cloud Config Server),客户端应用需要被通知到这些更改。
  5. 触发刷新事件: 客户端应用可以通过多种方式触发刷新事件,比如通过Spring Cloud Bus广播配置更改消息,或者直接调用/actuator/refresh端点。
  6. 更新本地的Environment对象: 在刷新事件被触发之前或之后,需要更新本地的Environment对象,以反映外部配置源中的最新配置。这通常是通过Environment的实现类(如StandardEnvironment或MutablePropertySources)来完成的。更新的方式可能是添加、修改或删除PropertySource,或者直接操作MutablePropertySources中的属性源列表。
  7. 刷新作用域中的Bean: 当Environment对象更新后,RefreshScope会遍历其缓存中的所有Bean,对它们进行销毁和重新创建。这是通过调用GenericScope提供的生命周期管理方法来完成的。旧的Bean实例被销毁,新的Bean实例根据最新的配置(从更新后的Environment中获取)被创建并缓存。
  8. 应用新的配置: 经过刷新操作后,应用中的Bean将使用新的配置。由于@RefreshScope仅影响标记了此注解的Bean,因此未标记的Bean不会受到影响。

总结一下,要实现动态刷新,主要达成以下两个核心目标:

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

三、源码解读

1. 通过打断点查看堆栈,获取RefreshScope注解的Bean

因为类被加上了@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;
  }
            }
        }

从Spring框架的源码中可以印证上述说法。对于使用@RefreshScope(或其他自定义作用域)的Bean,Spring容器在创建Bean实例后会将其缓存到相应作用域的cache中。在后续请求相同Bean时,Spring会优先从这个缓存中尝试获取Bean实例。


如果缓存中是null,说明Bean尚未被创建或者已经被销毁,此时Spring会重新走一遍创建Bean的流程,包括解析Bean定义、执行依赖注入等步骤,最终将新创建的Bean实例再次缓存到作用域中。


这种缓存机制确保了对于相同作用域和相同Bean定义的请求,Spring能够快速地提供Bean实例,而不必每次都重新创建。同时,对于像@RefreshScope这样的特殊作用域,它还允许在运行时动态地刷新Bean实例,以适应配置的变更。在刷新过程中,缓存中的旧Bean实例会被销毁,新的Bean实例会被创建并缓存起来,以供后续使用。

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

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

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();
 }

当配置中心发生配置更改时,通常会通过Spring Cloud Bus或Spring Cloud Config的监控机制来传播这些更改。一旦客户端应用检测到这些更改,它会触发一个刷新事件,通常是RefreshEvent。


RefreshEventListener类实现了SmartApplicationListener接口,用于监听RefreshEvent事件。当RefreshEvent被触发时,handle方法会被调用,进而执行刷新逻辑。


在刷新逻辑中,首先会刷新Spring的Environment对象,这通常涉及重新加载配置属性。然后,会调用RefreshScope的refreshAll方法来刷新所有标记为@RefreshScope的Bean。


RefreshScope是GenericScope的一个扩展,它提供了额外的逻辑来处理配置刷新。在refreshAll方法中,通过调用父类的destroy方法来销毁当前作用域中缓存的所有Bean实例。这个销毁过程会将缓存中的Bean实例设置为null,并且释放相关的资源。一旦Bean实例被销毁,下次请求该Bean时,Spring容器将重新创建它。


销毁完成后,RefreshScope会发布一个RefreshScopeRefreshedEvent事件,以通知其他监听器刷新操作已经完成。


总的来说,这个过程确保了当配置发生更改时,应用能够动态地更新其Environment和@RefreshScope标记的Bean,而无需重启整个应用。这是Spring Cloud提供的一个强大特性,使得微服务应用能够在运行时动态地响应配置更改。


四、总结

通过结合@RefreshScope注解、RefreshScope和GenericScope的实现,以及Spring容器对Bean生命周期的管理,Spring Cloud能够实现配置的动态刷新。这使得微服务应用能够在不重启整个应用的情况下,响应外部配置的更改,从而提高了系统的灵活性和响应速度。


需要注意的是,虽然动态刷新配置是一个非常有用的特性,但它也有一些限制和注意事项。例如,不是所有的Bean都适合被标记为@RefreshScope,因为重新创建Bean实例可能会导致一些状态丢失。此外,频繁的配置更改和刷新可能会对系统的性能和稳定性产生影响。因此,在使用动态刷新配置时,需要权衡利弊,并谨慎选择需要刷新的Bean和配置。


希望本文能够帮助您更好地理解Spring Cloud中@RefreshScope实现动态刷新的原理,并在实际项目中正确地应用这个特性。


相关文章
|
22天前
|
消息中间件 监控 Java
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
31 6
|
22天前
|
Java 关系型数据库 MySQL
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
40 5
|
22天前
|
缓存 监控 Java
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
31 5
|
2月前
|
负载均衡 算法 Java
蚂蚁面试:Nacos、Sentinel了解吗?Springcloud 核心底层原理,你知道多少?
40岁老架构师尼恩分享了关于SpringCloud核心组件的底层原理,特别是针对蚂蚁集团面试中常见的面试题进行了详细解析。内容涵盖了Nacos注册中心的AP/CP模式、Distro和Raft分布式协议、Sentinel的高可用组件、负载均衡组件的实现原理等。尼恩强调了系统化学习的重要性,推荐了《尼恩Java面试宝典PDF》等资料,帮助读者更好地准备面试,提高技术实力,最终实现“offer自由”。更多技术资料和指导,可关注公众号【技术自由圈】获取。
蚂蚁面试:Nacos、Sentinel了解吗?Springcloud 核心底层原理,你知道多少?
|
2月前
|
负载均衡 Java API
Spring Cloud原理详解
Spring Cloud原理详解
81 0
|
2月前
|
负载均衡 Java 网络架构
Spring Cloud原理详解
介绍了Spring Cloud的原理和核心组件,包括服务注册与发现、配置管理、负载均衡、断路器、智能路由、分布式消息传递、分布式追踪和服务熔断等,旨在帮助开发人员快速构建和管理微服务架构中的分布式系统。
79 0
|
5月前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
587 15
|
5月前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
127 3
|
5月前
|
消息中间件 Java Nacos
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
80 0
|
Java Spring
Spring原理学习系列之五:IOC原理之Bean加载
其实很多同学都想通过阅读框架的源码以汲取框架设计思想以及编程营养,Spring框架其实就是个很好的框架源码学习对象。我们都知道Bean是Spring框架的最小操作单元,Spring框架通过对于Bean的统一管理实现其IOC以及AOP等核心的框架功能,那么Spring框架是如何把Bean加载到环境中来进行管理的呢?本文将围绕这个话题进行详细的阐述,并配合Spring框架的源码解析。
Spring原理学习系列之五:IOC原理之Bean加载