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 依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

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

@Component
@RefreshScope
public class RefleshBean {
    @Value("${config.property}")
    private String configProperty;

    public String getConfigProperty() {
        return configProperty;
    }
}

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

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

package org.springframework.cloud.context.config.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;

/**
 * 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;

}

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

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

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

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

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

public class StandardScopeCache implements ScopeCache {
 
    private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();    
    // ...
    public Object put(String name, Object value) {
        Object result = this.cache.putIfAbsent(name, value);
        if (result != null) {
            return result;
        }
        return value;
    }
}

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

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED,
        matchIfMissing = true)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
public class RefreshAutoConfiguration {    
 
    @Bean
    @ConditionalOnMissingBean(RefreshScope.class)
    public static RefreshScope refreshScope() {
        return new RefreshScope();
    }
    
    @Bean
    @ConditionalOnMissingBean
    public ContextRefresher contextRefresher(ConfigurableApplicationContext context,
            RefreshScope scope) {
        return new ContextRefresher(context, scope);
    }
    // ...
}

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

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

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

    @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);
            }
        }
        if (!errors.isEmpty()) {
            throw wrapIfNecessary(errors.get(0));
        }
        this.errors.clear();
    }

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

public synchronized Set<String> refreshEnvironment() {
    Map<String, Object> before = extract(
            this.context.getEnvironment().getPropertySources());
    addConfigFilesToEnvironment();
    Set<String> keys = changes(before,
            extract(this.context.getEnvironment().getPropertySources())).keySet();
    this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
    return keys;
}

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


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

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

还没有评论