Springcloud中的@RefreshScope详解

2024-06-04 9220阅读

一、概述

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

    org.springframework.cloud
    spring-cloud-starter-config

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 @Bean 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 cache = new ConcurrentHashMap();	
	// ...
	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 refresh() {
	Set keys = refreshEnvironment();
	this.scope.refreshAll();
	return keys;
}

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

	@Override
	public void destroy() {
		List errors = new ArrayList();
		Collection 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 refreshEnvironment() {
	Map before = extract(
			this.context.getEnvironment().getPropertySources());
	addConfigFilesToEnvironment();
	Set 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或需要动态加载配置的场景比较合适。


    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]