阿里面試官問我Spring是如何解決循環依賴的時候,我一臉懵逼了!

目錄:

1、簡介

2、什麼是循環依賴?

3、Spring 是如何解決循環依賴的?

4、Spring 解決循環依賴的源碼分析

5、為什麼需要三級緩存呢?

6、循環依賴的其他情況

7、總結

簡介

下文將介紹Spring是如何解決循環依賴的,在下文中將要介紹以下什麼是循環依賴。以及從源碼層面上分析Spring是如何解決的,最後會介紹哪幾種情況下的循環依賴 是Spring 解決不了的。

Spring 如何解決循環依賴也是高頻面試題,面試官的可以考察你對循環依賴的理解,以及你對源碼的熟悉程度,以及哪些情況下的循環依賴是Spring解決不了的。

什麼是循環依賴?

所謂的循環依賴就是A依賴B,B依賴A,或者是A依賴B,B依賴C,C依賴A,最終形成一個環狀的依賴。


代碼示例如下:

從上面的代碼看,如果Spring不處理循環依賴,會怎麼樣?

IOC容器在讀取上面配置時,首先會初始化對象A,然後發現對象A依賴對象B,就去初始化對象那個B,初始化對象B的時候,又發現依賴A,需要初始化A,如果容器不去處理,那麼將會無限循環下去 ,直到內存溢出。

那麼Spring是如何解決循環依賴的呢?

Spring 是如何解決循環依賴的?

在初始化A時,即在調用構造方法時,會 產生一個早期引用,存在緩存中,然後A發現需要依賴B對象,容器再去初始化B對象,B對象初始化後(調用構造方法,也會產生早期引用,存在緩存中),A這時就得到了B的對象(已實例化,但是還沒有注入屬性),然後B對象發現需要依賴A對象,這時A對象已經有一個早期引用了,所以B直接依賴對象A,

什麼是早期引用呢?

即 對象實例化後(調用構造方法),還沒有進行屬性注入,如下圖:

在 注入屬性之前的對象A就是早期對象

Spring 依賴是通過三級緩存來實現的,分類如下:

1、三級緩存(singletonFactories)

用於存放 beanName和 初始化好的bean對象(屬性已經初始化好的)

private final Map singletonObjects = new ConcurrentHashMap(256);

用於存放bean工廠。bean 工廠所產生的bean是還未完成初始化的bean.bean工廠所生成的對象最終會被緩存到earlySingletonObjects(二級緩存)中,包裹對象ObjectFactory.getObject()早期對象。

放入時機:調用類的構造方法初始化之後。

ObjectFactory.getObject()->觸發getEarlyBeanReference()之後,才把我們的早期對象放入二級緩存中。

2、二級緩存(earlySingletonObjects)

存放 bean 工廠對象,用於解決循環依賴

private final Map> singletonFactories = new HashMap>(16);

在三級緩存放入二級緩存中調用:

早期對象:就是bean剛剛調用了構造方法,還來不及給bean對象的 屬性進行賦值的對象

3、一級緩存(singletonObjects)

用於存放beanName 和一個原始bean 早期bean(屬性未初始化)

private final Map earlySingletonObjects = new HashMap(16);

用於存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用。

Spring 解決循環依賴的源碼分析

我們來了解一下Spring IOC容器獲取bean實例的流程:

我們來分析一下上圖的流程:

這個流程從getBean開始,所有的邏輯都在doGetBean中,doGetBean首先調用getSington方法獲取bean實例,獲取的實例分為三種情況:

(1) 完全實例化好的bean(此時bean的屬性已經注入)

(2) 早期對象(bean已經調用構造方法進行初始化,但是屬性還沒有注入)

(3) 各級緩存中都沒有,則返回null

如果不為null,則調用 getObjectForBeanInstance 方法來返回bean。getObjectForBeanInstance 方法作用是如果Bean是FactoryBean,則調用其 getObject()方法。

如果bean為null,則 調用getSingleton方法,

sharedInstance = getSingleton(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

try {

return createBean(beanName, mbd, args);

}

}

});

最終會調用createBean方法,進行bean的實例化。

首先看下調用鏈;

AbstractBeanFactory# doGetBean

1、從緩存中獲取bean對象

Object sharedInstance = getSingleton(beanName);

方法如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

//第一步

Object singletonObject = this.singletonObjects.get(beanName);

i

f (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

synchronized (this.singletonObjects) {

singletonObject = this.earlySingletonObjects.get(beanName);

if (singletonObject == null && allowEarlyReference) {

ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);

if (singletonFactory != null) {

singletonObject = singletonFactory.getObject();

this.earlySingletonObjects.put(beanName, singletonObject);

this.singletonFactories.remove(beanName);

}

}

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

執行步驟如下:

第一步: 我們嘗試從一級緩存中去獲取對象,一般直接從map中獲取是可以直接使用的,IOC容器初始化加載單實例bean的 時候第一次進來,一般為空

第二步: 若在一級緩存中沒有獲取到對象,並且此bean正在初始化,singletonsCurrentlyInCreation這個list包含該beanName,主要用於判斷bean是否在創建中。

第三步:嘗試去二級緩存中獲取對象(二級緩存中的 對象也是一個早期對象(已經調用構造方法,但是還沒有對屬性賦值)。

第四步:二級緩存中也沒有,那就直接從三級緩存中獲取ObjectFactory對象,這個對象就是用來解決循環依賴 的關鍵所在,

第五步:如果有三級緩存,則調用ObjectFactory包裝對象中,通過調用它的getObject()來獲取我們的早期對象,然後把早期對象放入二級緩存,從三級緩存中刪除掉。

2、如果緩存中沒有獲取到對象,下面就開始創建對象

if (mbd.isSingleton()) {

sharedInstance = getSingleton(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

try {

return createBean(beanName, mbd, args);

}

catch (BeansException ex) {

throw ex;

}

}

});

bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

}

public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {

synchronized (this.singletonObjects) {

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null) {

try {

//調用getObject-->createBean()去創建對象

singletonObject = singletonFactory.getObject();

newSingleton = true;

}

if (newSingleton) {

//把創建好的對象加入到緩存中,並且把早期對象從緩存中刪除,此時bean已經完全初始化好

addSingleton(beanName, singletonObject);

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

}

protected void addSingleton(String beanName, Object singletonObject) {

synchronized (this.singletonObjects) {

// 放入到緩存對象中

this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

//早期對象逸出

this.singletonFactories.remove(beanName);

this.earlySingletonObjects.remove(beanName);

this.registeredSingletons.add(beanName);

}

}

上面的代碼主要用來創建對象,調用ObjectFactory.getObject()方法創建對象,創建完成後,把早期 對象從緩存中刪除。

3、下面看創建對象的方法

AbstractAutowireCapableBeanFactory#doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)

throws BeanCreationException {

// Instantiate the bean.

BeanWrapper instanceWrapper = null;

// 調用構造方法創建bean對象,並且把bean對象包裹為beanwrapper對象

instanceWrapper = createBeanInstance(beanName, mbd, args);

// earlySingletonExposure 用於表示是否提前暴露原始對象的引用,用於解決循環依賴。

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&

isSingletonCurrentlyInCreation(beanName));

if (earlySingletonExposure) {

//把beanName -->ObjectFactory 存放在 singletonFactories緩存中

addSingletonFactory(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

return getEarlyBeanReference(beanName, mbd, bean);

}

});

}

// Initialize the bean instance.

Object exposedObject = bean;

try {

//早期對象進行賦值

populateBean(beanName, mbd, instanceWrapper);

if (exposedObject != null) {

//調用bean的後置處理器以及bean的初始化方法

exposedObject = initializeBean(beanName, exposedObject, mbd);

}

}

return exposedObject;

}

上面方法是真正創建對象的方法,步驟如下

1、調用bean的構造方法進行初始化

2、加入三級緩存中

3、早期對象進行賦值

4、調用Bean的後置處理器以及bean的初始化方法

下面看addSingletonFactory方法源碼:

protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) {

synchronized (this.singletonObjects) {

if (!this.singletonObjects.containsKey(beanName)) {

this.singletonFactories.put(beanName, singletonFactory);

this.earlySingletonObjects.remove(beanName);

this.registeredSingletons.add(beanName);

}

}

}

上述方法是把暴露的objectFactory對象放入singletonFactories中。

以上的過程對應的流程圖為:

為什麼需要三級緩存呢?

有人說,二級緩存不就夠了嗎?

答案是:二級緩存足夠了,但是沒有很好的擴展性,我們看下加入三級緩存的源碼:

addSingletonFactory(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

return getEarlyBeanReference(beanName, mbd, bean);

}

});

那麼 從緩存中獲取bean的代碼:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

synchronized (this.singletonObjects) {

singletonObject = this.earlySingletonObjects.get(beanName);

if (singletonObject == null && allowEarlyReference) {

ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);

if (singletonFactory != null) {

singletonObject = singletonFactory.getObject();

this.earlySingletonObjects.put(beanName, singletonObject);

this.singletonFactories.remove(beanName);

}

}

}

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

從上面代碼可以看出,當獲取三級緩存時,需要使用objectFactory.getObject()方法,最終會調用

getEarlyBeanReference(beanName, mbd, bean);方法,

getEarlyBeanReference 方法源碼:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

Object exposedObject = bean;

if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {

for (BeanPostProcessor bp : getBeanPostProcessors()) {

if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {

SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;

exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);

if (exposedObject == null) {

return null;

}

}

}

}

return exposedObject;

}

getEarlyBeanReference()方法的作用是:經過一系列的後置處理來給我們早期對象進行特殊化處理

從三級緩存中獲取包裝對象的時候 ,他會經過一次後置處理器的處理對我們早期對象的bean進行

特殊處理,但是Spring原生後置處理器沒有經過處理,是留給我們的擴展接口。

循環依賴的其他情況

1、構造方法循環依賴能解決嗎?

我們先看下示例代碼:

報錯信息如下:

報錯信息如上圖:

大概意思是:Spring 解決不了構造方法的循環依賴。

那麼為什麼Spring不能解決循環依賴呢?

從我們上面分析可知,如果是成員屬性有循環依賴,那麼Spring是可以解決的,是因為有早期對象的存在才可以解決,早期對象就是實例化後的對象(即調用構造方法後的實例)。

如果構造方法中循環依賴,則無法生成早期對象。所以,Spring無法解決構造方法依賴。

2、多例的循環依賴能解決嗎?

因為Spring只緩存單例對象,而不緩存多例對象,所以無法從緩存中存活

總結

到這裡,本編文章就基本上寫完了。由於本人技術能力有限,若文章有錯誤不妥之處,歡迎大家指出來。好了,本篇文章到此結束,謝謝大家的閱讀。