0


Springcloud中的@RefreshScope详解

一、概述
@RefreshScope注解是Spring Cloud中的一个注解,它用来实现Bean中属性的动态刷新,这意味着您可以在不停止和重新启动应用程序的情况下更改配置,它在微服务配置中心的场景下经常出现。

二、@RefreshScope 实现动态刷新的原理
1.在应用程序中使用 @RefreshScope 注解时,这个注解内部使用了@Scope注解,并将其值设置为"refresh",定义了一个新的作用域名为refresh。

2.当Spring容器启动时,它会解析所有的Bean定义,并遇到@RefreshScope注解时,Spring容器会知道这是一个特殊的作用域。它使用RefreshScope类(继承自GenericScope)来处理这些Bean的生命周期

3.当应用首次请求一个被@RefreshScope标记的Bean时,Spring容器会调用RefreshScope的get方法来创建Bean的实例,创建完成后,这个Bean实例会被缓存在RefreshScope中,以便后续快速获取。

4.在应用运行时,如果外部配置源中的配置发生了更改(比如通过 Nacos Server),客户端应用需要被通知到这些更改。

5.客户端应用可以通过多种方式触发刷新事件,比如通过Spring Cloud Bus广播配置更改消息。

6.在刷新事件被触发之前或之后,需要更新本地的Environment对象,以反映外部配置源中的最新配置。

7.当Environment对象更新后,RefreshScope会遍历其缓存中的所有Bean,对它们进行销毁和重新创建。这是通过调用GenericScope提供的生命周期管理方法来完成的。旧的Bean实例被销毁,新的Bean实例根据最新的配置(从更新后的Environment中获取)被创建并缓存。

8.经过刷新操作后,应用中的Bean将使用新的配置。由于@RefreshScope仅影响标记了此注解的Bean,因此未标记的Bean不会受到影响。

三、如何在 Spring Boot中使用 @RefreshScope?
1.添加 相关的Maven 依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-config</artifactId>
  4. </dependency>

2.创建一个需要刷新的bean对象。

  1. @Component
  2. @RefreshScope
  3. public class RefleshBean {
  4. @Value("${config.property}")
  5. private String configProperty;
  6. public String getConfigProperty() {
  7. return configProperty;
  8. }
  9. }

上面我们使用 @RefreshScope 注解标记 RefleshBean 类。这意味着当 config.property属性更改时,Spring Boot 将重新加载这个 bean。

四、@RefreshScope 源码解析
1.首先看下@RefreshScope 注解

  1. package org.springframework.cloud.context.config.annotation;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. import org.springframework.context.annotation.Scope;
  8. import org.springframework.context.annotation.ScopedProxyMode;
  9. /**
  10. * Convenience annotation to put a <code>@Bean</code> definition in
  11. * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}.
  12. * Beans annotated this way can be refreshed at runtime and any components that are using
  13. * them will get a new instance on the next method call, fully initialized and injected
  14. * with all dependencies.
  15. *
  16. * @author Dave Syer
  17. *
  18. */
  19. @Target({ ElementType.TYPE, ElementType.METHOD })
  20. @Retention(RetentionPolicy.RUNTIME)
  21. @Scope("refresh")
  22. @Documented
  23. public @interface RefreshScope {
  24. /**
  25. * @see Scope#proxyMode()
  26. * @return proxy mode
  27. */
  28. ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
  29. }

可以看出其是一个复合注解,被标注了 @Scope(“refresh”),@RefreshScope 是scopeName="refresh"的 @Scope。

2.RefreshScope的实现
2.1 RefreshScope 继承GenericScope,其父类GenericScope的get方法实现获取Bean,注意创建Bean还是由IOC#createBean实现。
GenericScope类

  1. @Override
  2. public Object get(String name, ObjectFactory<?> objectFactory) {
  3. //通过cache把bean缓存下来,如果不存在则创建
  4. BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
  5. this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
  6. try {
  7. return value.getBean();
  8. }
  9. catch (RuntimeException e) {
  10. this.errors.put(name, e);
  11. throw e;
  12. }
  13. }

GenericScope 里面的 get 方法负责对象的创建和缓存。

上面代码中看似每次都新创建一个对象放入缓存中,实际上是创建了一个objectFactory的封装对象,并没有真正创建对象。而cache的put逻辑最终实现为map的putIfAbsent,即缓存中已存在key则返回原来的value。实现在 StandardScopeCache类

  1. public class StandardScopeCache implements ScopeCache {
  2. private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();
  3. // ...
  4. public Object put(String name, Object value) {
  5. Object result = this.cache.putIfAbsent(name, value);
  6. if (result != null) {
  7. return result;
  8. }
  9. return value;
  10. }
  11. }

2.2 RefreshScope缓存清理。
配置更新后需要清除RefreshScope中的缓存,ContextRefresher负责完成这一任务。它由RefreshAutoConfiguration引入,创建的时候会自动注入RefreshScope和context。

  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnClass(RefreshScope.class)
  3. @ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED,
  4. matchIfMissing = true)
  5. @AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
  6. public class RefreshAutoConfiguration {
  7. @Bean
  8. @ConditionalOnMissingBean(RefreshScope.class)
  9. public static RefreshScope refreshScope() {
  10. return new RefreshScope();
  11. }
  12. @Bean
  13. @ConditionalOnMissingBean
  14. public ContextRefresher contextRefresher(ConfigurableApplicationContext context,
  15. RefreshScope scope) {
  16. return new ContextRefresher(context, scope);
  17. }
  18. // ...
  19. }

2.3 ContextRefresher的refresh方法就是清理RefreshScope缓存的入口。

  1. public synchronized Set<String> refresh() {
  2. Set<String> keys = refreshEnvironment();
  3. this.scope.refreshAll();
  4. return keys;
  5. }

其中refreshAll最终会落实到GenericScope的destroy方法,其中清理了所有的缓存。

  1. @Override
  2. public void destroy() {
  3. List<Throwable> errors = new ArrayList<Throwable>();
  4. Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
  5. for (BeanLifecycleWrapper wrapper : wrappers) {
  6. try {
  7. Lock lock = this.locks.get(wrapper.getName()).writeLock();
  8. lock.lock();
  9. try {
  10. wrapper.destroy();
  11. }
  12. finally {
  13. lock.unlock();
  14. }
  15. }
  16. catch (RuntimeException e) {
  17. errors.add(e);
  18. }
  19. }
  20. if (!errors.isEmpty()) {
  21. throw wrapIfNecessary(errors.get(0));
  22. }
  23. this.errors.clear();
  24. }

2.4 重新加载
想实现动态刷新配置,光清除RefreshScope的缓存还不够,还要具备重新加载配置到context中的能力,这一任务也是ContextRefresher完成的。
实际上就是在refresh方法中清理RefreshScope缓存之前,即refreshEnvironment方法中完成了配置的重新加载。

  1. public synchronized Set<String> refreshEnvironment() {
  2. Map<String, Object> before = extract(
  3. this.context.getEnvironment().getPropertySources());
  4. addConfigFilesToEnvironment();
  5. Set<String> keys = changes(before,
  6. extract(this.context.getEnvironment().getPropertySources())).keySet();
  7. this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
  8. return keys;
  9. }

总结:
带有@RefreshScope注解的Bean在配置发生变化时进行刷新,可以确保配置的动态生效。但是,使用@RefreshScope并不是必须的。如果你希望配置的变化立即生效,并且不想手动刷新Bean,可以直接使用@ConfigurationProperties注解来获取配置项的值,这样配置的变化会立即反映在应用程序中。使用@RefreshScope的目的是延迟Bean的刷新,只在需要的时候才进行刷新。这对于一些开销较大的Bean或需要动态加载配置的场景比较合适。


本文转载自: https://blog.csdn.net/qq_42077317/article/details/137992880
版权归原作者 夜空下的星 所有, 如有侵权,请联系我们删除。

“Springcloud中的@RefreshScope详解”的评论:

还没有评论