Spring Bean 循環依賴為什么需要三級緩存


Spring Bean 循環依賴為什么需要三級緩存

這里指的是單例的、非構造依賴的循環引用。很多人都知道Spring用了三層緩存來解決循環依賴,但是不知道其原因,為什么是三級緩存?二級緩存不行嗎?一級緩存不可以 ?

三級緩存

Spring 解決循環依賴的核心就是提前暴露對象,而提前暴露的對象就是放置於第二級緩存中。緩存的底層都是Map,至於它們屬於第幾層是由Spring獲取數據順序以及其作用來表現的。

三級緩存的說明:

名稱 作用
singletonObjects 一級緩存,存放完整的 Bean。
earlySingletonObjects 二級緩存,存放提前暴露的Bean,Bean 是不完整的,未完成屬性注入和執行 初始化(init) 方法。
singletonFactories 三級緩存,存放的是 Bean 工廠,主要是生產 Bean,存放到二級緩存中。

DefaultSingletonBeanRegistry類中:

	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

為什么使用三級緩存?

第一 先說說第一級緩存singletonObjects

1.先說一級緩存singletonObjects。實際上,一級依賴已經可以解決循環依賴的問題,假設兩個beanA和beanB相互依賴,beanA被實例化后,放入一級緩存,即使沒有進行初始化,但是beanA的引用已經創建(棧到堆的引用已經確定),其他依賴beanB已經可以持有beanA的引用,但是這個bean在沒有初始化完成前,其內存(堆)里的字段、方法等還不能正常使用,but,這並不影響對象之間引用持有;這個時候beanA依賴的beanB實例化,beanB可以順利拿到beanA的引用,完成beanB的實例化與初始化,並放入一級緩存,在beanB完成創建后,beanA通過緩存順利拿到beanB的引用,至此,循環依賴只需一層緩存就能完成。

2.一級緩存的關鍵點在與:bean實例化與初始化的分離。從JVM的角度,實例化后,對象已經存在,其內的屬性都是初始默認值,只有在初始化后才會賦值,以及持有其他對象的引用。通過這個特性,在實例化后,我們就可以將對象的引用放入緩存交給需要引用依賴的其他對象,這個過程就是提前暴露。

第二 說說第三級緩存singletonFactories

1.上述我們通過一級緩存已經拿到的對象有什么問題?

根本問題就是,我們拿到的是bean的原始引用,如果我們需要的是bean的代理對象怎么辦?Spring里充斥了大量的動態代理模式的架構,典型的AOP就是動態代理模式實現的,再比如我們經常使用的配置類注解@Configuration在缺省情況下(full mode),其內的所有@Bean都是處於動態代理模式,除非手動指定proxyBeanMethods = false將配置轉成簡略模式(lite mode)。

2.所以,Spring在bean實例化后,將原始bean放入第三級緩存singletonFactories中,第三級緩存里實際存入的是ObjectFactory接口簽名的回調實現。

# 函數簽名
addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)
    
# 具體實現由回調決定    
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

那么如果有動態代理的需求,里面可以埋點進行處理,將原始bean包裝后返回。

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

3.通過第三級緩存我們可以拿到可能經過包裝的對象,解決對象代理封裝的問題。

第三 說說第二級緩存earlySingletonObjects

1.為什么需要earlySingletonObjects這個二級緩存?並且,如果只有一個緩存的情況下,為什么不直接使用singletonFactories這個緩存,即可實現代理又可以緩存數據。

2.從軟件設計角度考慮,三個緩存代表三種不同的職責,根據單一職責原理,從設計角度就需分離三種職責的緩存,所以形成三級緩存的狀態。

3、再次說說三級緩存的划分及其作用。

一級緩存singletonObjects是完整的bean,它可以被外界任意使用,並且不會有歧義。

二級緩存earlySingletonObjects是不完整的bean,沒有完成初始化,它與singletonObjects的分離主要是職責的分離以及邊界划分,可以試想一個Map緩存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在復雜度很高的架構中,很容易出現歧義,並帶來一些不可預知的錯誤。

三級緩存singletonFactories,其職責就是包裝一個bean,有回調邏輯,所以它的作用非常清晰,並且只能處於第三層。

在實際使用中,要獲取一個bean,先從一級緩存一直查找到三級緩存,緩存bean的時候是從三級到一級的順序保存,並且緩存bean的過程中,三個緩存都是互斥的,只會保持bean在一個緩存中,而且,最終都會在一級緩存中。

總結

還不到總結的時候... ...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM