java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Springcloud @RefreshScope

Springcloud中的@RefreshScope的实现

作者:夜空下的星

@RefreshScope注解是Spring Cloud中的一个注解,它用来实现Bean中属性的动态刷新,本文就来介绍一下@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或需要动态加载配置的场景比较合适。

到此这篇关于Springcloud中的@RefreshScope的实现的文章就介绍到这了,更多相关Springcloud @RefreshScope内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文