一、概述
@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或需要动态加载配置的场景比较合适。