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