Springcloud中的@RefreshScope的實(shí)現(xiàn)
一、概述
@RefreshScope注解是Spring Cloud中的一個(gè)注解,它用來實(shí)現(xiàn)Bean中屬性的動(dòng)態(tài)刷新,這意味著您可以在不停止和重新啟動(dòng)應(yīng)用程序的情況下更改配置,它在微服務(wù)配置中心的場景下經(jīng)常出現(xiàn)。
二、@RefreshScope 實(shí)現(xiàn)動(dòng)態(tài)刷新的原理
1.在應(yīng)用程序中使用 @RefreshScope 注解時(shí),這個(gè)注解內(nèi)部使用了@Scope注解,并將其值設(shè)置為"refresh",定義了一個(gè)新的作用域名為refresh。
2.當(dāng)Spring容器啟動(dòng)時(shí),它會解析所有的Bean定義,并遇到@RefreshScope注解時(shí),Spring容器會知道這是一個(gè)特殊的作用域。它使用RefreshScope類(繼承自GenericScope)來處理這些Bean的生命周期
3.當(dāng)應(yīng)用首次請求一個(gè)被@RefreshScope標(biāo)記的Bean時(shí),Spring容器會調(diào)用RefreshScope的get方法來創(chuàng)建Bean的實(shí)例,創(chuàng)建完成后,這個(gè)Bean實(shí)例會被緩存在RefreshScope中,以便后續(xù)快速獲取。
4.在應(yīng)用運(yùn)行時(shí),如果外部配置源中的配置發(fā)生了更改(比如通過 Nacos Server),客戶端應(yīng)用需要被通知到這些更改。
5.客戶端應(yīng)用可以通過多種方式觸發(fā)刷新事件,比如通過Spring Cloud Bus廣播配置更改消息。
6.在刷新事件被觸發(fā)之前或之后,需要更新本地的Environment對象,以反映外部配置源中的最新配置。
7.當(dāng)Environment對象更新后,RefreshScope會遍歷其緩存中的所有Bean,對它們進(jìn)行銷毀和重新創(chuàng)建。這是通過調(diào)用GenericScope提供的生命周期管理方法來完成的。舊的Bean實(shí)例被銷毀,新的Bean實(shí)例根據(jù)最新的配置(從更新后的Environment中獲?。┍粍?chuàng)建并緩存。
8.經(jīng)過刷新操作后,應(yīng)用中的Bean將使用新的配置。由于@RefreshScope僅影響標(biāo)記了此注解的Bean,因此未標(biāo)記的Bean不會受到影響。
三、如何在 Spring Boot中使用 @RefreshScope?
1.添加 相關(guān)的Maven 依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
2.創(chuàng)建一個(gè)需要刷新的bean對象。
@Component @RefreshScope public class RefleshBean { @Value("${config.property}") private String configProperty; public String getConfigProperty() { return configProperty; } }
上面我們使用 @RefreshScope 注解標(biāo)記 RefleshBean 類。這意味著當(dāng) config.property屬性更改時(shí),Spring Boot 將重新加載這個(gè) 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; }
可以看出其是一個(gè)復(fù)合注解,被標(biāo)注了 @Scope(“refresh”),@RefreshScope 是scopeName="refresh"的 @Scope。
2.RefreshScope的實(shí)現(xiàn)
2.1 RefreshScope 繼承GenericScope,其父類GenericScope的get方法實(shí)現(xiàn)獲取Bean,注意創(chuàng)建Bean還是由IOC#createBean實(shí)現(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 方法負(fù)責(zé)對象的創(chuàng)建和緩存。
上面代碼中看似每次都新創(chuàng)建一個(gè)對象放入緩存中,實(shí)際上是創(chuàng)建了一個(gè)objectFactory的封裝對象,并沒有真正創(chuàng)建對象。而cache的put邏輯最終實(shí)現(xiàn)為map的putIfAbsent,即緩存中已存在key則返回原來的value。實(shí)現(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負(fù)責(zé)完成這一任務(wù)。它由RefreshAutoConfiguration引入,創(chuàng)建的時(shí)候會自動(dò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最終會落實(shí)到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 重新加載
想實(shí)現(xiàn)動(dòng)態(tài)刷新配置,光清除RefreshScope的緩存還不夠,還要具備重新加載配置到context中的能力,這一任務(wù)也是ContextRefresher完成的。
實(shí)際上就是在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; }
總結(jié):
帶有@RefreshScope注解的Bean在配置發(fā)生變化時(shí)進(jìn)行刷新,可以確保配置的動(dòng)態(tài)生效。但是,使用@RefreshScope并不是必須的。如果你希望配置的變化立即生效,并且不想手動(dòng)刷新Bean,可以直接使用@ConfigurationProperties注解來獲取配置項(xiàng)的值,這樣配置的變化會立即反映在應(yīng)用程序中。使用@RefreshScope的目的是延遲Bean的刷新,只在需要的時(shí)候才進(jìn)行刷新。這對于一些開銷較大的Bean或需要?jiǎng)討B(tài)加載配置的場景比較合適。
到此這篇關(guān)于Springcloud中的@RefreshScope的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Springcloud @RefreshScope內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot和Swagger結(jié)合提高API開發(fā)效率
這篇文章主要介紹了SpringBoot和Swagger結(jié)合提高API開發(fā)效率的相關(guān)資料,需要的朋友可以參考下2017-09-09java 實(shí)現(xiàn)將Object類型轉(zhuǎn)換為int類型
這篇文章主要介紹了java 實(shí)現(xiàn)將Object類型轉(zhuǎn)換為int類型的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07spring Cloud微服務(wù)跨域?qū)崿F(xiàn)步驟
這篇文章主要介紹了spring Cloud微服務(wù)跨域?qū)崿F(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11SpringBoot獲取application.properties文件中文亂碼問題及解決
這篇文章主要介紹了SpringBoot獲取application.properties文件中文亂碼問題及解決,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05JAVA如何判斷上傳文件后綴名是否符合規(guī)范MultipartFile
這篇文章主要介紹了JAVA判斷上傳文件后綴名是否符合規(guī)范MultipartFile,文中通過實(shí)例代碼介紹了java實(shí)現(xiàn)對上傳文件做安全性檢查,需要的朋友可以參考下2023-11-11idea?intellij快速修復(fù)if語句缺少大括號的問題
這篇文章主要介紹了idea?intellij快速修復(fù)if語句缺少大括號的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04Feign如何實(shí)現(xiàn)第三方的HTTP請求
這篇文章主要介紹了Feign如何實(shí)現(xiàn)第三方的HTTP請求,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10SpringBoot使用easy-captcha 實(shí)現(xiàn)驗(yàn)證碼登錄功能(解決思路)
文章介紹了如何使用Spring Boot和Easy-Captcha實(shí)現(xiàn)驗(yàn)證碼登錄功能,后端通過Easy-Captcha生成驗(yàn)證碼并存儲在Redis中,前端獲取驗(yàn)證碼并顯示給用戶,登錄時(shí),前端將用戶輸入的驗(yàn)證碼和標(biāo)識符發(fā)送到后端進(jìn)行驗(yàn)證,感興趣的朋友跟隨小編一起看看吧2025-02-02spring mail借助qq郵箱服務(wù)器發(fā)送郵件
這篇文章主要介紹了spring mail借助qq郵箱服務(wù)器發(fā)送郵件的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12