首頁 > 軟體

SpringCloud @RefreshScope重新整理機制淺析

2023-08-24 18:00:58

一、前言

用過Spring Cloud的同學都知道在使用動態設定重新整理的我們要設定一個@RefreshScope 在類上才可以實現物件屬性的的動態更新,本著知其所以然的態度,晚上沒事兒又把這個點回顧了一下,下面就來簡單的說下自己的理解。

總覽下,實現@RefreshScope 動態重新整理的就需要以下幾個:

  • @ Scope
  • @RefreshScope
  • RefreshScope
  • GenericScope
  • Scope
  • ContextRefresher

二、@Scope

一句話,@RefreshScope 能實現動態重新整理全仰仗著@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是單例的。(預設)
     *  prototype    表示該bean是多例的,即每次使用該bean時都會新建一個物件。
     *  request        在一次http請求中,一個bean對應一個範例。
     *  session        在一個httpSession中,一個bean對應一個範例
	 */
	@AliasFor("value")
	String scopeName() default "";
	/**
    *   DEFAULT			不使用代理。(預設)
	* 	NO				不使用代理,等價於DEFAULT。
	* 	INTERFACES		使用基於介面的代理(jdk dynamic proxy)。
	* 	TARGET_CLASS	使用基於類的代理(cglib)。
    */
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

通過程式碼我們可以清晰的看到兩個主要屬性value 和 proxyMode,value就不多說了,大家平時經常用看看註解就可以。proxyMode 這個就有意思了,而這個就是@RefreshScope 實現的本質了。

我們需要關心的就是ScopedProxyMode.TARGET_CLASS 這個屬性,當ScopedProxyMode 為TARGET_CLASS 的時候會給當前建立的bean 生成一個代理物件,會通過代理物件來存取,每次存取都會建立一個新的物件。

理解起來可能比較晦澀,那先來看下實現再回頭來看這句話。

三、RefreshScope 的實現原理

先來看下@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 ,其內部就一個屬性預設 ScopedProxyMode.TARGET_CLASS。知道了是通過Spring Scope 來實現的那就簡單了,我們來看下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); 這個方法幫助我們來建立一個新的bean ,也就是說,@RefreshScope 在呼叫 重新整理的時候會使用此方法來給我們建立新的物件,這樣就可以通過spring 的裝配機制將屬性重新注入了,也就實現了所謂的動態重新整理。

那它究竟是怎麼處理老的物件,又怎麼除法建立新的物件呢?

在開頭我提過幾個重要的類,而其中 RefreshScope extends GenericScope, GenericScope implements Scope。

所以通過檢視程式碼,是GenericScope 實現了 Scope 最重要的 get(String name, ObjectFactory<?> objectFactory) 方法,在GenericScope 裡面 包裝了一個內部類 BeanLifecycleWrapperCache 來對加了 @RefreshScope 從而建立的物件進行快取,使其在不重新整理時獲取的都是同一個物件。(這裡你可以把 BeanLifecycleWrapperCache 想象成為一個大Map 快取了所有@RefreshScope 標註的物件)

知道了物件是快取的,所以在進行動態重新整理的時候,只需要清除快取,重新建立就好了。

來看程式碼,眼見為實,只留下關鍵方法:

// ContextRefresher 外面使用它來進行方法呼叫 ============================== 我是分割線
	public synchronized Set<String> refresh() {
		Set<String> keys = refreshEnvironment();
		this.scope.refreshAll();
		return keys;
	}
// RefreshScope 內部程式碼  ============================== 我是分割線
	@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 裡的方法 ============================== 我是分割線
	//進行物件獲取,如果沒有就建立並放入快取
	@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;
		}
	}
	//進行快取的資料清理
	@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 就是外層呼叫方法用的,GenericScope 裡面的 get 方法負責物件的建立和快取,destroy 方法負責再重新整理時快取的清理工作。當然spring n內部還進行很多其他有趣的處理,有興趣的同學可以詳細看一下。

四、總結

綜上所述,來總結下@RefreshScope 實現流程

  • 需要動態重新整理的類標註@RefreshScope 註解
  • @RefreshScope 註解標註了@Scope 註解,並預設了ScopedProxyMode.TARGET_CLASS; 屬性,此屬性的功能就是在建立一個代理,在每次呼叫的時候都用它來呼叫GenericScope get 方法來獲取物件
  • 如屬性發生變更會呼叫 ContextRefresher refresh() -》RefreshScope refreshAll() 進行快取清理方法呼叫,並行送重新整理事件通知 -》 GenericScope 真正的 清理方法destroy() 實現清除快取
  • 在下一次使用物件的時候,會呼叫GenericScope get(String name, ObjectFactory<?> objectFactory) 方法建立一個新的物件,並存入快取中,此時新物件因為Spring 的裝配機制就是新的屬性了。

以上就是SpringCloud @RefreshScope重新整理機制淺析的詳細內容,更多關於SpringCloud @RefreshScope的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com