Spring 解決循環依賴必須要三級緩存嗎?

我們都知道 Spring 是通過三級緩存來解決循環依賴的,但是解決循環依賴真的需要使用到三級緩衝嗎?只使用兩級緩存是否可以呢?本篇文章就 Spring 是如何使用三級緩存解決循環依賴作為引子,驗證兩級緩存是否可以解決循環依賴。

循環依賴

既然要解決循環依賴,那麼就要知道循環依賴是什麼。如下圖所示:

Spring 解決循環依賴必須要三級緩存嗎?

Spring 解決循環依賴必須要三級緩存嗎?

通過上圖,我們可以看出:

  • A 依賴於 B
  • B 依賴於 C
  • C 依賴於 A
<code>

public

class

A

{

private

B

b; }

public

class

B

{

private

C

c

; }

public

class

C

{

private

A

a; } 複製代碼/<code>
Spring 解決循環依賴必須要三級緩存嗎?

這種依賴關係形成了一種閉環,從而造成了循環依賴的局面。

下面是未解決循環依賴的常規步驟:

  1. 實例化 A,此時 A 還未完成屬性填充和初始化方法(@PostConstruct)的執行。
  2. A 對象發現需要注入 B 對象,但是容器中並沒有 B 對象(如果對象創建完成並且屬性注入完成和執行完初始化方法就會放入容器中)。
  3. 實例化 B,此時 B 還未完成屬性填充和初始化方法(@PostConstruct)的執行。
  4. B 對象發現需要注入 C 對象,但是容器中並沒有 C 對象。
  5. 實例化 C,此時 C 還未完成屬性填充和初始化方法(@PostConstruct)的執行。
  6. C 對象發現需要注入 A 對象,但是容器中並沒有 A 對象。
  7. 重複步驟 1。

三級緩存

Spring 解決循環依賴的核心就是提前暴露對象,而提前暴露的對象就是放置於第二級緩存中。下表是三級緩存的說明:

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

所有被 Spring 管理的 Bean,最終都會存放在 singletonObjects 中,這裡面存放的 Bean 是經歷了所有生命週期的(除了銷燬的生命週期),完整的,可以給用戶使用的。

earlySingletonObjects 存放的是已經被實例化,但是還沒有注入屬性和執行 init 方法的 Bean。

singletonFactories 存放的是生產 Bean 的工廠。

Bean 都已經實例化了,為什麼還需要一個生產 Bean 的工廠呢?這裡實際上是跟 AOP 有關,如果項目中不需要為 Bean 進行代理,那麼這個 Bean 工廠就會直接返回一開始實例化的對象,如果需要使用 AOP 進行代理,那麼這個工廠就會發揮重要的作用了,這也是本文需要重點關注的問題之一。

解決循環依賴

Spring 是如何通過上面介紹的三級緩存來解決循環依賴的呢?這裡只用 A,B 形成的循環依賴來舉例:

  1. 實例化 A,此時 A 還未完成屬性填充和初始化方法(@PostConstruct)的執行,A 只是一個半成品。
  2. 為 A 創建一個 Bean 工廠,並放入到 singletonFactories 中。
  3. 發現 A 需要注入 B 對象,但是一級、二級、三級緩存均為發現對象 B。
  4. 實例化 B,此時 B 還未完成屬性填充和初始化方法(@PostConstruct)的執行,B 只是一個半成品。
  5. 為 B 創建一個 Bean 工廠,並放入到 singletonFactories 中。
  6. 發現 B 需要注入 A 對象,此時在一級、二級未發現對象 A,但是在三級緩存中發現了對象 A,從三級緩存中得到對象 A,並將對象 A 放入二級緩存中,同時刪除三級緩存中的對象 A。(注意,此時的 A 還是一個半成品,並沒有完成屬性填充和執行初始化方法)
  7. 將對象 A 注入到對象 B 中。
  8. 對象 B 完成屬性填充,執行初始化方法,並放入到一級緩存中,同時刪除二級緩存中的對象 B。(此時對象 B 已經是一個成品)
  9. 對象 A 得到對象 B,將對象 B 注入到對象 A 中。(對象 A 得到的是一個完整的對象 B)
  10. 對象 A 完成屬性填充,執行初始化方法,並放入到一級緩存中,同時刪除二級緩存中的對象 A。

我們從源碼中來分析整個過程:

創建 Bean 的方法在
AbstractAutowireCapableBeanFactory::doCreateBean()

<code>

protected

Object doCreateBean(

final

String beanName,

final

RootBeanDefinition mbd, Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper =

null

;

if

(instanceWrapper ==

null

) { instanceWrapper =

this

.createBeanInstance(beanName, mbd, args); }

final

Object bean = instanceWrapper !=

null

? instanceWrapper.getWrappedInstance() :

null

; Class> beanType = instanceWrapper !=

null

? instanceWrapper.getWrappedClass() :

null

; boolean earlySingletonExposure = (mbd.isSingleton() &&

this

.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));

if

(earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }

this

.populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd);

return

exposedObject; } 複製代碼/<code>
Spring 解決循環依賴必須要三級緩存嗎?

添加三級緩存的方法如下:

<code>

protected

void

addSingletonFactory

(String beanName, ObjectFactory> singletonFactory)

{ Assert.notNull(singletonFactory,

"Singleton factory must not be null"

);

synchronized

(

this

.singletonObjects) {

if

(!

this

.singletonObjects.containsKey(beanName)) {

this

.singletonFactories.put(beanName, singletonFactory);

this

.earlySingletonObjects.remove(beanName);

this

.registeredSingletons.add(beanName); } } }

public

interface

ObjectFactory

<

T

>

{

T

getObject

()

throws

BeansException

; } 複製代碼/<code>
Spring 解決循環依賴必須要三級緩存嗎?

通過這段代碼,我們可以知道 Spring 在實例化對象的之後,就會為其創建一個 Bean 工廠,並將此工廠加入到三級緩存中。

因此,Spring 一開始提前暴露的並不是實例化的 Bean,而是將 Bean 包裝起來的 ObjectFactory。為什麼要這麼做呢?

這實際上涉及到 AOP,如果創建的 Bean 是有代理的,那麼注入的就應該是代理 Bean,而不是原始的 Bean。但是 Spring 一開始並不知道 Bean 是否會有循環依賴,通常情況下(沒有循環依賴的情況下),Spring 都會在完成填充屬性,並且執行完初始化方法之後再為其創建代理。但是,如果出現了循環依賴的話,Spring 就不得不為其提前創建代理對象,否則注入的就是一個原始對象,而不是代理對象。因此,這裡就涉及到應該在哪裡提前創建代理對象?

Spring 的做法就是在 ObjectFactory 中去提前創建代理對象。它會執行 getObject() 方法來獲取到 Bean。實際上,它真正執行的方法如下:

<code>

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;

}

複製代碼

/<code>
Spring 解決循環依賴必須要三級緩存嗎?

因為提前進行了代理,避免對後面重複創建代理對象,會在 earlyProxyReferences 中記錄已被代理的對象。

<code>

public

abstract

class

AbstractAutoProxyCreator

extends

ProxyProcessorSupport

implements

SmartInstantiationAwareBeanPostProcessor

,

BeanFactoryAware

{

public

Object

getEarlyBeanReference

(Object bean, String beanName)

{ Object cacheKey = getCacheKey(bean.getClass(), beanName);

this

.earlyProxyReferences.put(cacheKey, bean);

return

wrapIfNecessary(bean, beanName, cacheKey); } } 複製代碼/<code>
Spring 解決循環依賴必須要三級緩存嗎?

通過上面的解析,我們可以知道 Spring 需要三級緩存的目的是為了在沒有循環依賴的情況下,延遲代理對象的創建,使 Bean 的創建符合 Spring 的設計原則。

如何獲取依賴

我們目前已經知道了 Spring 的三級依賴的作用,但是 Spring 在注入屬性的時候是如何去獲取依賴的呢?

他是通過一個 getSingleton() 方法去獲取所需要的 Bean 的。

<code>

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; } 複製代碼/<code>
Spring 解決循環依賴必須要三級緩存嗎?

當 Spring 為某個 Bean 填充屬性的時候,它首先會尋找需要注入對象的名稱,然後依次執行 getSingleton() 方法得到所需注入的對象,而獲取對象的過程就是先從一級緩存中獲取,一級緩存中沒有就從二級緩存中獲取,二級緩存中沒有就從三級緩存中獲取,如果三級緩存中也沒有,那麼就會去執行 doCreateBean() 方法創建這個 Bean。

二級緩存

我們現在已經知道,第三級緩存的目的是為了延遲代理對象的創建,因為如果沒有依賴循環的話,那麼就不需要為其提前創建代理,可以將它延遲到初始化完成之後再創建。

既然目的只是延遲的話,那麼我們是不是可以不延遲創建,而是在實例化完成之後,就為其創建代理對象,這樣我們就不需要第三級緩存了。因此,我們可以將addSingletonFactory() 方法進行改造。

<code>

protected

void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) { Assert.notNull(singletonFactory,

"Singleton factory must not be null"

); synchronized (

this

.singletonObjects) {

if

(!

this

.singletonObjects.containsKey(beanName)) {

object

o = singletonFactory.getObject();

this

.earlySingletonObjects.put(beanName, o);

this

.registeredSingletons.add(beanName); } } } 複製代碼/<code>
Spring 解決循環依賴必須要三級緩存嗎?

這樣的話,每次實例化完 Bean 之後就直接去創建代理對象,並添加到二級緩存中。測試結果是完全正常的Spring 的初始化時間應該也是不會有太大的影響,因為如果 Bean 本身不需要代理的話,是直接返回原始 Bean 的,並不需要走複雜的創建代理 Bean 的流程。

結論

測試證明,二級緩存也是可以解決循環依賴的。為什麼 Spring 不選擇二級緩存,而要額外多添加一層緩存呢?

如果 Spring 選擇二級緩存來解決循環依賴的話,那麼就意味著所有 Bean 都需要在實例化完成之後就立馬為其創建代理,而 Spring 的設計原則是在 Bean 初始化完成之後才為其創建代理。所以,Spring 選擇了三級緩存。但是因為循環依賴的出現,導致了 Spring 不得不提前去創建代理,因為如果不提前創建代理對象,那麼注入的就是原始對象,這樣就會產生錯誤。


分享到:


相關文章: