SpringCloud?@RefreshScope刷新機(jī)制淺析
一、前言
用過Spring Cloud的同學(xué)都知道在使用動(dòng)態(tài)配置刷新的我們要配置一個(gè)@RefreshScope 在類上才可以實(shí)現(xiàn)對(duì)象屬性的的動(dòng)態(tài)更新,本著知其所以然的態(tài)度,晚上沒事兒又把這個(gè)點(diǎn)回顧了一下,下面就來簡(jiǎn)單的說下自己的理解。
總覽下,實(shí)現(xiàn)@RefreshScope 動(dòng)態(tài)刷新的就需要以下幾個(gè):
- @ Scope
- @RefreshScope
- RefreshScope
- GenericScope
- Scope
- ContextRefresher
二、@Scope
一句話,@RefreshScope 能實(shí)現(xiàn)動(dòng)態(tài)刷新全仰仗著@Scope 這個(gè)注解,這是為什么呢?
@Scope 代表了Bean的作用域,我們來看下其中的屬性:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
/**
* Alias for {@link #scopeName}.
* @see #scopeName
*/
@AliasFor("scopeName")
String value() default "";
/**
* singleton 表示該bean是單例的。(默認(rèn))
* prototype 表示該bean是多例的,即每次使用該bean時(shí)都會(huì)新建一個(gè)對(duì)象。
* request 在一次http請(qǐng)求中,一個(gè)bean對(duì)應(yīng)一個(gè)實(shí)例。
* session 在一個(gè)httpSession中,一個(gè)bean對(duì)應(yīng)一個(gè)實(shí)例
*/
@AliasFor("value")
String scopeName() default "";
/**
* DEFAULT 不使用代理。(默認(rèn))
* NO 不使用代理,等價(jià)于DEFAULT。
* INTERFACES 使用基于接口的代理(jdk dynamic proxy)。
* TARGET_CLASS 使用基于類的代理(cglib)。
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
通過代碼我們可以清晰的看到兩個(gè)主要屬性value 和 proxyMode,value就不多說了,大家平時(shí)經(jīng)常用看看注解就可以。proxyMode 這個(gè)就有意思了,而這個(gè)就是@RefreshScope 實(shí)現(xiàn)的本質(zhì)了。
我們需要關(guān)心的就是ScopedProxyMode.TARGET_CLASS 這個(gè)屬性,當(dāng)ScopedProxyMode 為TARGET_CLASS 的時(shí)候會(huì)給當(dāng)前創(chuàng)建的bean 生成一個(gè)代理對(duì)象,會(huì)通過代理對(duì)象來訪問,每次訪問都會(huì)創(chuàng)建一個(gè)新的對(duì)象。
理解起來可能比較晦澀,那先來看下實(shí)現(xiàn)再回頭來看這句話。
三、RefreshScope 的實(shí)現(xiàn)原理
先來看下@RefreshScope
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
2. 可以看出,它使用就是 @Scope ,其內(nèi)部就一個(gè)屬性默認(rèn) ScopedProxyMode.TARGET_CLASS。知道了是通過Spring Scope 來實(shí)現(xiàn)的那就簡(jiǎn)單了,我們來看下Scope 這個(gè)接口
public interface Scope {
/**
* Return the object with the given name from the underlying scope,
* {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
* if not found in the underlying storage mechanism.
* <p>This is the central operation of a Scope, and the only operation
* that is absolutely required.
* @param name the name of the object to retrieve
* @param objectFactory the {@link ObjectFactory} to use to create the scoped
* object if it is not present in the underlying storage mechanism
* @return the desired object (never {@code null})
* @throws IllegalStateException if the underlying scope is not currently active
*/
Object get(String name, ObjectFactory<?> objectFactory);
@Nullable
Object remove(String name);
void registerDestructionCallback(String name, Runnable callback);
@Nullable
Object resolveContextualObject(String key);
@Nullable
String getConversationId();
}
看下接口,我們只看Object get(String name, ObjectFactory<?> objectFactory); 這個(gè)方法幫助我們來創(chuàng)建一個(gè)新的bean ,也就是說,@RefreshScope 在調(diào)用 刷新的時(shí)候會(huì)使用此方法來給我們創(chuàng)建新的對(duì)象,這樣就可以通過spring 的裝配機(jī)制將屬性重新注入了,也就實(shí)現(xiàn)了所謂的動(dòng)態(tài)刷新。
那它究竟是怎么處理老的對(duì)象,又怎么除法創(chuàng)建新的對(duì)象呢?
在開頭我提過幾個(gè)重要的類,而其中 RefreshScope extends GenericScope, GenericScope implements Scope。
所以通過查看代碼,是GenericScope 實(shí)現(xiàn)了 Scope 最重要的 get(String name, ObjectFactory<?> objectFactory) 方法,在GenericScope 里面 包裝了一個(gè)內(nèi)部類 BeanLifecycleWrapperCache 來對(duì)加了 @RefreshScope 從而創(chuàng)建的對(duì)象進(jìn)行緩存,使其在不刷新時(shí)獲取的都是同一個(gè)對(duì)象。(這里你可以把 BeanLifecycleWrapperCache 想象成為一個(gè)大Map 緩存了所有@RefreshScope 標(biāo)注的對(duì)象)
知道了對(duì)象是緩存的,所以在進(jìn)行動(dòng)態(tài)刷新的時(shí)候,只需要清除緩存,重新創(chuàng)建就好了。
來看代碼,眼見為實(shí),只留下關(guān)鍵方法:
// ContextRefresher 外面使用它來進(jìn)行方法調(diào)用 ============================== 我是分割線
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
// RefreshScope 內(nèi)部代碼 ============================== 我是分割線
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
// GenericScope 里的方法 ============================== 我是分割線
//進(jìn)行對(duì)象獲取,如果沒有就創(chuàng)建并放入緩存
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
//進(jìn)行緩存的數(shù)據(jù)清理
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = 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();
}通過觀看源代碼我們得知,我們截取了三個(gè)片段所得之,ContextRefresher 就是外層調(diào)用方法用的,GenericScope 里面的 get 方法負(fù)責(zé)對(duì)象的創(chuàng)建和緩存,destroy 方法負(fù)責(zé)再刷新時(shí)緩存的清理工作。當(dāng)然spring n內(nèi)部還進(jìn)行很多其他有趣的處理,有興趣的同學(xué)可以詳細(xì)看一下。
四、總結(jié)
綜上所述,來總結(jié)下@RefreshScope 實(shí)現(xiàn)流程
- 需要?jiǎng)討B(tài)刷新的類標(biāo)注@RefreshScope 注解
- @RefreshScope 注解標(biāo)注了@Scope 注解,并默認(rèn)了ScopedProxyMode.TARGET_CLASS; 屬性,此屬性的功能就是在創(chuàng)建一個(gè)代理,在每次調(diào)用的時(shí)候都用它來調(diào)用GenericScope get 方法來獲取對(duì)象
- 如屬性發(fā)生變更會(huì)調(diào)用 ContextRefresher refresh() -》RefreshScope refreshAll() 進(jìn)行緩存清理方法調(diào)用,并發(fā)送刷新事件通知 -》 GenericScope 真正的 清理方法destroy() 實(shí)現(xiàn)清理緩存
- 在下一次使用對(duì)象的時(shí)候,會(huì)調(diào)用GenericScope get(String name, ObjectFactory<?> objectFactory) 方法創(chuàng)建一個(gè)新的對(duì)象,并存入緩存中,此時(shí)新對(duì)象因?yàn)镾pring 的裝配機(jī)制就是新的屬性了。
以上就是SpringCloud @RefreshScope刷新機(jī)制淺析的詳細(xì)內(nèi)容,更多關(guān)于SpringCloud @RefreshScope的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于SpringBoot解決CORS跨域的問題(@CrossOrigin)
這篇文章主要介紹了基于SpringBoot解決CORS跨域的問題(@CrossOrigin),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01
java返回的List進(jìn)行add操作報(bào)錯(cuò)
本文主要介紹了java返回的List進(jìn)行add操作報(bào)錯(cuò),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
幾種常見mybatis分頁(yè)的實(shí)現(xiàn)方式
這篇文章主要介紹了幾種常見mybatis分頁(yè)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
Lombok同時(shí)使?@Data和@Builder踩坑總結(jié)
這篇文章主要介紹了Lombok同時(shí)使?@Data和@Builder踩坑總結(jié),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助2022-05-05
@Transactional注解異常報(bào)錯(cuò)之多數(shù)據(jù)源詳解
這篇文章主要介紹了@Transactional注解異常報(bào)錯(cuò)之多數(shù)據(jù)源詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
idea 解決用骨架創(chuàng)建項(xiàng)目過慢的操作方式
這篇文章主要介紹了idea 解決用骨架創(chuàng)建項(xiàng)目過慢的操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
將SpringBoot項(xiàng)目無縫部署到Tomcat服務(wù)器的操作流程
SpringBoot 是一個(gè)用來簡(jiǎn)化 Spring 應(yīng)用初始搭建以及開發(fā)過程的框架,我們可以通過內(nèi)置的 Tomcat 容器來輕松地運(yùn)行我們的應(yīng)用,本文給大家介紹 SpringBoot 項(xiàng)目部署到獨(dú)立 Tomcat 服務(wù)器的操作流程,需要的朋友可以參考下2024-05-05

