Spring循環(huán)依賴實現(xiàn)過程揭秘
概述
我們在日常的技術(shù)交流中經(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實例之間的關(guān)系,是已經(jīng)實例化和初始化完成的bean */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** * 二級緩存 * 用于保存beanName和創(chuàng)建bean實例之間的關(guān)系,與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的工廠之間的關(guān)系,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();
}
}關(guān)鍵代碼
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屬性:設(shè)置此bean定義是否是"synthetic",一般是指只有AOP相關(guān)的pointCut配置或者Advice配置才會將 synthetic設(shè)置為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對應(yīng)的單例對象
Object singletonObject = this.singletonObjects.get(beanName);
// 如果單例對象緩存中沒有,并且該beanName對應(yīng)的單例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方法將對應(yīng)的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) {
// 將映射關(guān)系添加到單例對象的高速緩存中
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。
到此這篇關(guān)于Spring循環(huán)依賴實現(xiàn)過程揭秘的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java poi sax方式處理大數(shù)據(jù)量excel文件
這篇文章主要介紹了java poi sax方式處理大數(shù)據(jù)量excel文件,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
springboot controller 增加指定前綴的兩種實現(xiàn)方法
這篇文章主要介紹了springboot controller 增加指定前綴的兩種實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
springboot項目打包鏡像方式以及區(qū)分環(huán)境打包的方法
本文主要介紹了springboot項目打包鏡像方式以及區(qū)分環(huán)境打包的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-03-03
Java實現(xiàn)復(fù)雜的進制轉(zhuǎn)換器功能示例
這篇文章主要介紹了Java實現(xiàn)復(fù)雜的進制轉(zhuǎn)換器功能,結(jié)合實例形式分析了java數(shù)學(xué)運算的相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-01-01
解決IntelliJ?IDEA輸出中文顯示為問號問題的有效方法
最近剛學(xué)到文件字節(jié)流這里,但輸出中文時,出現(xiàn)了控制臺輸出問號的情況,所以下面這篇文章主要給大家介紹了關(guān)于如何解決IntelliJ?IDEA輸出中文顯示為問號問題的有效方法,需要的朋友可以參考下2022-07-07

