SpringCloud?@RefreshScope刷新機(jī)制淺析
一、前言
用過Spring Cloud的同學(xué)都知道在使用動態(tài)配置刷新的我們要配置一個@RefreshScope 在類上才可以實(shí)現(xiàn)對象屬性的的動態(tài)更新,本著知其所以然的態(tài)度,晚上沒事兒又把這個點(diǎn)回顧了一下,下面就來簡單的說下自己的理解。
總覽下,實(shí)現(xiàn)@RefreshScope 動態(tài)刷新的就需要以下幾個:
- @ Scope
- @RefreshScope
- RefreshScope
- GenericScope
- Scope
- ContextRefresher
二、@Scope
一句話,@RefreshScope 能實(shí)現(xiàn)動態(tài)刷新全仰仗著@Scope 這個注解,這是為什么呢?
@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時都會新建一個對象。 * request 在一次http請求中,一個bean對應(yīng)一個實(shí)例。 * session 在一個httpSession中,一個bean對應(yīng)一個實(shí)例 */ @AliasFor("value") String scopeName() default ""; /** * DEFAULT 不使用代理。(默認(rèn)) * NO 不使用代理,等價于DEFAULT。 * INTERFACES 使用基于接口的代理(jdk dynamic proxy)。 * TARGET_CLASS 使用基于類的代理(cglib)。 */ ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; }
通過代碼我們可以清晰的看到兩個主要屬性value 和 proxyMode,value就不多說了,大家平時經(jīng)常用看看注解就可以。proxyMode 這個就有意思了,而這個就是@RefreshScope 實(shí)現(xiàn)的本質(zhì)了。
我們需要關(guān)心的就是ScopedProxyMode.TARGET_CLASS 這個屬性,當(dāng)ScopedProxyMode 為TARGET_CLASS 的時候會給當(dāng)前創(chuàng)建的bean 生成一個代理對象,會通過代理對象來訪問,每次訪問都會創(chuàng)建一個新的對象。
理解起來可能比較晦澀,那先來看下實(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)部就一個屬性默認(rèn) ScopedProxyMode.TARGET_CLASS。知道了是通過Spring Scope 來實(shí)現(xiàn)的那就簡單了,我們來看下Scope 這個接口
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); 這個方法幫助我們來創(chuàng)建一個新的bean ,也就是說,@RefreshScope 在調(diào)用 刷新的時候會使用此方法來給我們創(chuàng)建新的對象,這樣就可以通過spring 的裝配機(jī)制將屬性重新注入了,也就實(shí)現(xiàn)了所謂的動態(tài)刷新。
那它究竟是怎么處理老的對象,又怎么除法創(chuàng)建新的對象呢?
在開頭我提過幾個重要的類,而其中 RefreshScope extends GenericScope, GenericScope implements Scope。
所以通過查看代碼,是GenericScope 實(shí)現(xiàn)了 Scope 最重要的 get(String name, ObjectFactory<?> objectFactory) 方法,在GenericScope 里面 包裝了一個內(nèi)部類 BeanLifecycleWrapperCache 來對加了 @RefreshScope 從而創(chuàng)建的對象進(jìn)行緩存,使其在不刷新時獲取的都是同一個對象。(這里你可以把 BeanLifecycleWrapperCache 想象成為一個大Map 緩存了所有@RefreshScope 標(biāo)注的對象)
知道了對象是緩存的,所以在進(jìn)行動態(tài)刷新的時候,只需要清除緩存,重新創(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)行對象獲取,如果沒有就創(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(); }
通過觀看源代碼我們得知,我們截取了三個片段所得之,ContextRefresher 就是外層調(diào)用方法用的,GenericScope 里面的 get 方法負(fù)責(zé)對象的創(chuàng)建和緩存,destroy 方法負(fù)責(zé)再刷新時緩存的清理工作。當(dāng)然spring n內(nèi)部還進(jìn)行很多其他有趣的處理,有興趣的同學(xué)可以詳細(xì)看一下。
四、總結(jié)
綜上所述,來總結(jié)下@RefreshScope 實(shí)現(xiàn)流程
- 需要動態(tài)刷新的類標(biāo)注@RefreshScope 注解
- @RefreshScope 注解標(biāo)注了@Scope 注解,并默認(rèn)了ScopedProxyMode.TARGET_CLASS; 屬性,此屬性的功能就是在創(chuàng)建一個代理,在每次調(diào)用的時候都用它來調(diào)用GenericScope get 方法來獲取對象
- 如屬性發(fā)生變更會調(diào)用 ContextRefresher refresh() -》RefreshScope refreshAll() 進(jìn)行緩存清理方法調(diào)用,并發(fā)送刷新事件通知 -》 GenericScope 真正的 清理方法destroy() 實(shí)現(xiàn)清理緩存
- 在下一次使用對象的時候,會調(diào)用GenericScope get(String name, ObjectFactory<?> objectFactory) 方法創(chuàng)建一個新的對象,并存入緩存中,此時新對象因?yàn)镾pring 的裝配機(jī)制就是新的屬性了。
以上就是SpringCloud @RefreshScope刷新機(jī)制淺析的詳細(xì)內(nèi)容,更多關(guān)于SpringCloud @RefreshScope的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot中快速實(shí)現(xiàn)郵箱發(fā)送代碼解析
這篇文章主要介紹了SpringBoot中快速實(shí)現(xiàn)郵箱發(fā)送代碼解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-08-08簡單講解奇偶排序算法及在Java數(shù)組中的實(shí)現(xiàn)
這篇文章主要介紹了奇偶排序算法及Java數(shù)組的實(shí)現(xiàn),奇偶排序的時間復(fù)雜度為O(N^2),需要的朋友可以參考下2016-04-04smslib發(fā)短信實(shí)例代碼(電腦發(fā)短信)
smslib發(fā)短信實(shí)例,大家可以參考使用開發(fā)自己的程序2013-12-12Springboot JPA打印SQL語句及參數(shù)的實(shí)現(xiàn)
在SpringBoot項(xiàng)目中調(diào)試和優(yōu)化數(shù)據(jù)庫操作是很常見的需求,本文主要介紹了Springboot JPA打印SQL語句及參數(shù)的實(shí)現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-06-06Java數(shù)據(jù)類型轉(zhuǎn)換實(shí)例解析
這篇文章主要介紹了Java數(shù)據(jù)類型轉(zhuǎn)換實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11SpringBoot 啟動報錯Unable to connect to 
這篇文章主要介紹了SpringBoot 啟動報錯Unable to connect to Redis server: 127.0.0.1/127.0.0.1:6379問題的解決方案,文中通過圖文結(jié)合的方式給大家講解的非常詳細(xì),對大家解決問題有一定的幫助,需要的朋友可以參考下2024-10-10