
首先,让我们来了解一下@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;
}@Scope注解用于指定由Spring IoC容器管理的Bean的作用域。作用域决定了Bean的生命周期、创建时机以及存储方式。常见的Spring作用域包括singleton(单例)、prototype(原型)、request(请求)、session(会话)等。
在Spring Cloud中,@RefreshScope实现动态刷新的流程可以总结为以下几个步骤:
总结一下,要实现动态刷新,主要达成以下两个核心目标:

因为类被加上了@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);
}
}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实例会被创建并缓存起来,以供后续使用。

配置中心发生变化后,会收到一个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();
}总的来说,这个过程确保了当配置发生更改时,应用能够动态地更新其Environment和@RefreshScope标记的Bean,而无需重启整个应用。这是Spring Cloud提供的一个强大特性,使得微服务应用能够在运行时动态地响应配置更改。
通过结合@RefreshScope注解、RefreshScope和GenericScope的实现,以及Spring容器对Bean生命周期的管理,Spring Cloud能够实现配置的动态刷新。这使得微服务应用能够在不重启整个应用的情况下,响应外部配置的更改,从而提高了系统的灵活性和响应速度。
需要注意的是,虽然动态刷新配置是一个非常有用的特性,但它也有一些限制和注意事项。例如,不是所有的Bean都适合被标记为@RefreshScope,因为重新创建Bean实例可能会导致一些状态丢失。此外,频繁的配置更改和刷新可能会对系统的性能和稳定性产生影响。因此,在使用动态刷新配置时,需要权衡利弊,并谨慎选择需要刷新的Bean和配置。
希望本文能够帮助您更好地理解Spring Cloud中@RefreshScope实现动态刷新的原理,并在实际项目中正确地应用这个特性。
术因分享而日新,每获新知,喜溢心扉。 诚邀关注公众号 『
码到三十五』 ,获取更多技术资料。