Spring循環(huán)依賴實(shí)現(xiàn)過程揭秘
概述
我們?cè)谌粘5募夹g(shù)交流中經(jīng)常會(huì)提到Spring循環(huán)依賴,聽起來挺高大尚的,那Spring到底是如何實(shí)現(xiàn)的呢?下面我們就來一一揭秘。
什么是循環(huán)依賴
如上圖所示,A對(duì)象中包含B對(duì)象的引用,同時(shí)B對(duì)象中包含A對(duì)象的引用;也就是在創(chuàng)建A的過程中需要同時(shí)創(chuàng)建B對(duì)象,這就是所謂的循環(huán)依賴。
下面是普通對(duì)象創(chuàng)建的流程圖,也正是循環(huán)依賴產(chǎn)生的根因。
可以看到A對(duì)象創(chuàng)建依賴B對(duì)象創(chuàng)建,B對(duì)象創(chuàng)建依賴A對(duì)象創(chuàng)建,形成了閉環(huán),造成死循環(huán)了。
Spring三級(jí)緩存介紹
上圖形成了一個(gè)閉環(huán),如果想解決這個(gè)問題,那么就必須保證不會(huì)出現(xiàn)第二次創(chuàng)建A對(duì)象這個(gè)步驟,也就是說從容器中獲取A的時(shí)候必須要能夠獲取到。
Spring中的如何解決的呢?
在Spring中,對(duì)象的創(chuàng)建可以分為實(shí)例化和初始化,實(shí)例化好但未完成初始化的對(duì)象是可以直接給其他對(duì)象引用的,所以此時(shí)可以做一件事,把完成實(shí)例化但未初始化的對(duì)象提前暴露出去,讓其他對(duì)象能夠進(jìn)行引用,就完成了這個(gè)閉環(huán)的解環(huán)操作。
這就是常說的提前暴露對(duì)象。
spring中的三級(jí)緩存分別是什么
在spring源碼的DefaultSingletonBeanRegistry.java類中
/** * 一級(jí)緩存 * 用于保存beanName和創(chuàng)建bean實(shí)例之間的關(guān)系,是已經(jīng)實(shí)例化和初始化完成的bean */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** * 二級(jí)緩存 * 用于保存beanName和創(chuàng)建bean實(shí)例之間的關(guān)系,與singletonObject的區(qū)別是這里面的bean是半成品,只完成實(shí)例化沒有完成初始化; * 與singletonFactories的區(qū)別是,當(dāng)一個(gè)單例bean被放到這里之后,那么當(dāng)bean還在創(chuàng)建過程中就可以通過getBean方法獲取到,可以方便進(jìn)行循環(huán)依賴的檢測(cè)。 */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); /** * 三級(jí)緩存 * 用于保存beanName和和創(chuàng)建bean的工廠之間的關(guān)系,ObjectFactory就是一個(gè)Lambda表達(dá)式。 */ private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
三級(jí)緩存解決循環(huán)依賴流程
示例
@Component public class A { @Autowired private B b; public B getB() { return b; } public void setB(B b) { this.b = b; } } @Component public class B { @Autowired private A a; public A getA() { return a; } public void setA(A a) { this.a = a; } } @Configuration @ComponentScan(basePackages = "com.mashibing.selfcycle") public class CycleConfig { } public class TestCycle { public static void main(String[] args) { final AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(CycleConfig.class); ac.close(); } }
關(guān)鍵代碼
1、添加A對(duì)象工廠到三級(jí)緩存
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean // 為避免后期循環(huán)依賴,可以在bean初始化完成前將創(chuàng)建實(shí)例的ObjectFactory加入工廠 // bean是實(shí)例化后的bean對(duì)象 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { // 默認(rèn)最終公開的對(duì)象是bean,通過createBeanInstance創(chuàng)建出來的普通對(duì)象 Object exposedObject = bean; // mbd的systhetic屬性:設(shè)置此bean定義是否是"synthetic",一般是指只有AOP相關(guān)的pointCut配置或者Advice配置才會(huì)將 synthetic設(shè)置為true // 如果mdb不是synthetic且此工廠擁有InstantiationAwareBeanPostProcessor if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { // 遍歷工廠內(nèi)的所有后處理器 for (BeanPostProcessor bp : getBeanPostProcessors()) { // 如果bp是SmartInstantiationAwareBeanPostProcessor實(shí)例 if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; // 讓exposedObject經(jīng)過每個(gè)SmartInstantiationAwareBeanPostProcessor的包裝 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } }
2、填充屬性b,從beanFactory中獲取b對(duì)象
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) throws BeansException { return beanFactory.getBean(beanName); }
3、添加B對(duì)象工廠到三級(jí)緩存
源代碼流程與《1、添加A對(duì)象工廠到三級(jí)緩存,A的實(shí)例化對(duì)象到二級(jí)緩存》 一致。
4、添加B對(duì)象的屬性a,從beanFactory中獲取a對(duì)象
DefaultSingleBeanRegistry.java @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock // 從單例對(duì)象緩存中獲取beanName對(duì)應(yīng)的單例對(duì)象 Object singletonObject = this.singletonObjects.get(beanName); // 如果單例對(duì)象緩存中沒有,并且該beanName對(duì)應(yīng)的單例bean正在創(chuàng)建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //從早期單例對(duì)象緩存中獲取單例對(duì)象(之所稱成為早期單例對(duì)象,是因?yàn)閑arlySingletonObjects里 // 的對(duì)象的都是通過提前曝光的ObjectFactory創(chuàng)建出來的,還未進(jìn)行屬性填充等操作) singletonObject = this.earlySingletonObjects.get(beanName); // 如果在早期單例對(duì)象緩存中也沒有,并且允許創(chuàng)建早期單例對(duì)象引用 if (singletonObject == null && allowEarlyReference) { // 如果為空,則鎖定全局變量并進(jìn)行處理 synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 當(dāng)某些方法需要提前初始化的時(shí)候則會(huì)調(diào)用addSingletonFactory方法將對(duì)應(yīng)的ObjectFactory初始化策略存儲(chǔ)在singletonFactories ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 如果存在單例對(duì)象工廠,則通過工廠創(chuàng)建一個(gè)單例對(duì)象 singletonObject = singletonFactory.getObject(); // 記錄在緩存中,二級(jí)緩存和三級(jí)緩存的對(duì)象不能同時(shí)存在 this.earlySingletonObjects.put(beanName, singletonObject); // 從三級(jí)緩存中移除 this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
5、添加B對(duì)象到一級(jí)緩存,同時(shí)刪除二級(jí)、三級(jí)緩存
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // 將映射關(guān)系添加到單例對(duì)象的高速緩存中 this.singletonObjects.put(beanName, singletonObject); // 移除beanName在單例工廠緩存中的數(shù)據(jù) this.singletonFactories.remove(beanName); // 移除beanName在早期單例對(duì)象的高速緩存的數(shù)據(jù) this.earlySingletonObjects.remove(beanName); // 將beanName添加到已注冊(cè)的單例集中 this.registeredSingletons.add(beanName); } }
6、A對(duì)象賦值b屬性,添加到一級(jí)緩存,完成A對(duì)象的創(chuàng)建 流程圖
理論上使用二級(jí)緩存就能解決上面的循環(huán)依賴問題,為什么要三級(jí)緩存呢?
三級(jí)緩存存放的是objectFactory,并不是bean對(duì)象,也就是說是用來創(chuàng)建bean對(duì)象的。 對(duì)于動(dòng)態(tài)代理對(duì)象(比如切面切到的類),需要使用三級(jí)緩存來生成代理對(duì)象。 比如,如果A,B都被切點(diǎn)切中了:
1、A創(chuàng)建時(shí)首先生成A的實(shí)例化對(duì)象a,a對(duì)象的ObjectFactory放入三級(jí)緩存。
2、填充A對(duì)象的屬性b時(shí),先實(shí)例化B對(duì)象b,b對(duì)象的ObjectFactory放入三級(jí)緩存。
3、b對(duì)象填充a屬性時(shí),如果發(fā)現(xiàn)a需要代理,根據(jù)三級(jí)緩存生成a的代理對(duì)象a1。
4、b對(duì)象通過a1填充a屬性,在initialBean的時(shí)候走的beanPostProcessor方法是,生成b的代理對(duì)象b1,同時(shí)b1的a屬性是a1。
5、a對(duì)象從一級(jí)緩存獲取b1,并填充b屬性。
6、a對(duì)象在initialBean的時(shí)候走的beanPostProcessor方法是,從二級(jí)緩存獲取a1對(duì)象,同時(shí)a的b屬性是a1。
7、最后一級(jí)緩存中存放的就是a1,b1;a1的b屬性是b1,b1的a屬性是a1。
到此這篇關(guān)于Spring循環(huán)依賴實(shí)現(xiàn)過程揭秘的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java poi sax方式處理大數(shù)據(jù)量excel文件
這篇文章主要介紹了java poi sax方式處理大數(shù)據(jù)量excel文件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01springboot controller 增加指定前綴的兩種實(shí)現(xiàn)方法
這篇文章主要介紹了springboot controller 增加指定前綴的兩種實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02springboot項(xiàng)目打包鏡像方式以及區(qū)分環(huán)境打包的方法
本文主要介紹了springboot項(xiàng)目打包鏡像方式以及區(qū)分環(huán)境打包的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-03-03Java實(shí)現(xiàn)復(fù)雜的進(jìn)制轉(zhuǎn)換器功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)復(fù)雜的進(jìn)制轉(zhuǎn)換器功能,結(jié)合實(shí)例形式分析了java數(shù)學(xué)運(yùn)算的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-01-01解決IntelliJ?IDEA輸出中文顯示為問號(hào)問題的有效方法
最近剛學(xué)到文件字節(jié)流這里,但輸出中文時(shí),出現(xiàn)了控制臺(tái)輸出問號(hào)的情況,所以下面這篇文章主要給大家介紹了關(guān)于如何解決IntelliJ?IDEA輸出中文顯示為問號(hào)問題的有效方法,需要的朋友可以參考下2022-07-07SpringBoot?項(xiàng)目瘦身maven/gradle詳解
這篇文章主要介紹了SpringBoot項(xiàng)目瘦身(maven/gradle),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01