Springcloud中的@RefreshScope的實現(xiàn)
一、概述
@RefreshScope注解是Spring Cloud中的一個注解,它用來實現(xiàn)Bean中屬性的動態(tài)刷新,這意味著您可以在不停止和重新啟動應用程序的情況下更改配置,它在微服務配置中心的場景下經(jīng)常出現(xiàn)。
二、@RefreshScope 實現(xiàn)動態(tài)刷新的原理
1.在應用程序中使用 @RefreshScope 注解時,這個注解內(nèi)部使用了@Scope注解,并將其值設置為"refresh",定義了一個新的作用域名為refresh。
2.當Spring容器啟動時,它會解析所有的Bean定義,并遇到@RefreshScope注解時,Spring容器會知道這是一個特殊的作用域。它使用RefreshScope類(繼承自GenericScope)來處理這些Bean的生命周期
3.當應用首次請求一個被@RefreshScope標記的Bean時,Spring容器會調(diào)用RefreshScope的get方法來創(chuàng)建Bean的實例,創(chuàng)建完成后,這個Bean實例會被緩存在RefreshScope中,以便后續(xù)快速獲取。
4.在應用運行時,如果外部配置源中的配置發(fā)生了更改(比如通過 Nacos Server),客戶端應用需要被通知到這些更改。
5.客戶端應用可以通過多種方式觸發(fā)刷新事件,比如通過Spring Cloud Bus廣播配置更改消息。
6.在刷新事件被觸發(fā)之前或之后,需要更新本地的Environment對象,以反映外部配置源中的最新配置。
7.當Environment對象更新后,RefreshScope會遍歷其緩存中的所有Bean,對它們進行銷毀和重新創(chuàng)建。這是通過調(diào)用GenericScope提供的生命周期管理方法來完成的。舊的Bean實例被銷毀,新的Bean實例根據(jù)最新的配置(從更新后的Environment中獲?。┍粍?chuàng)建并緩存。
8.經(jīng)過刷新操作后,應用中的Bean將使用新的配置。由于@RefreshScope僅影響標記了此注解的Bean,因此未標記的Bean不會受到影響。
三、如何在 Spring Boot中使用 @RefreshScope?
1.添加 相關的Maven 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>2.創(chuàng)建一個需要刷新的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的實現(xiàn)
2.1 RefreshScope 繼承GenericScope,其父類GenericScope的get方法實現(xiàn)獲取Bean,注意創(chuàng)建Bean還是由IOC#createBean實現(xiàn)。
GenericScope類
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
//通過cache把bean緩存下來,如果不存在則創(chuàng)建
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 方法負責對象的創(chuàng)建和緩存。
上面代碼中看似每次都新創(chuàng)建一個對象放入緩存中,實際上是創(chuàng)建了一個objectFactory的封裝對象,并沒有真正創(chuàng)建對象。而cache的put邏輯最終實現(xiàn)為map的putIfAbsent,即緩存中已存在key則返回原來的value。實現(xiàn)在 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引入,創(chuàng)建的時候會自動注入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 重新加載
想實現(xiàn)動態(tài)刷新配置,光清除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在配置發(fā)生變化時進行刷新,可以確保配置的動態(tài)生效。但是,使用@RefreshScope并不是必須的。如果你希望配置的變化立即生效,并且不想手動刷新Bean,可以直接使用@ConfigurationProperties注解來獲取配置項的值,這樣配置的變化會立即反映在應用程序中。使用@RefreshScope的目的是延遲Bean的刷新,只在需要的時候才進行刷新。這對于一些開銷較大的Bean或需要動態(tài)加載配置的場景比較合適。
到此這篇關于Springcloud中的@RefreshScope的實現(xiàn)的文章就介紹到這了,更多相關Springcloud @RefreshScope內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot?AspectJ切面配合自定義注解實現(xiàn)權限校驗的示例詳解
本文章介紹了如何通過創(chuàng)建自定義的權限校驗注解,配合AspectJ切面攔截注解實現(xiàn)權限校驗,本文結合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2025-09-09
SpringBoot項目使用@Scheduled注解實現(xiàn)定時任務的方法
文章介紹了在SpringBoot項目中使用@Scheduled注解實現(xiàn)定時任務的三種方式:基于注解、基于接口和基于注解設定多線程定時任務,詳細講解了@Scheduled注解的使用方法、各個參數(shù)以及如何配置動態(tài)定時任務和多線程定時任務,感興趣的朋友一起看看吧2025-03-03

