爛大街的 Spring 迴圈依賴問題,你覺得自己會了嗎

阿寒聊Java發表於2021-06-16

文章已收錄在 GitHub JavaKeeper ,N 線網際網路開發、面試必備技能兵器譜,筆記自取。

微信搜「 JavaKeeper 」程式設計師成長充電站,網際網路技術武道場。無套路領取 500+ 本電子書和 30+ 視訊教學和原始碼。

前言

迴圈依賴問題,算是一道爛大街的面試題了,解毒之前,我們先來回顧兩個知識點:

初學 Spring 的時候,我們就知道 IOC,控制反轉麼,它將原本在程式中手動建立物件的控制權,交由 Spring 框架來管理,不需要我們手動去各種 new XXX

儘管是 Spring 管理,不也得建立物件嗎, Java 物件的建立步驟很多,可以 new XXX、序列化、clone() 等等, 只是 Spring 是通過反射 + 工廠的方式建立物件並放在容器的,建立好的物件我們一般還會對物件屬性進行賦值,才去使用,可以理解是分了兩個步驟。

好了,對這兩個步驟有個印象就行,接著我們進入迴圈依賴,先說下迴圈依賴的概念

什麼是迴圈依賴

所謂的迴圈依賴是指,A 依賴 B,B 又依賴 A,它們之間形成了迴圈依賴。或者是 A 依賴 B,B 依賴 C,C 又依賴 A,形成了迴圈依賴。更或者是自己依賴自己。它們之間的依賴關係如下:

這裡以兩個類直接相互依賴為例,他們的實現程式碼可能如下:

public class BeanB {
    private BeanA beanA;
    public void setBeanA(BeanA beanA) {
		this.beanA = beanA;
	}
}

public class BeanA {
    private BeanB beanB;
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
	}
}

配置資訊如下(用註解方式注入同理,只是為了方便理解,用了配置檔案):

<bean id="beanA" class="priv.starfish.BeanA">
  <property name="beanB" ref="beanB"/>
</bean>

<bean id="beanB" class="priv.starfish.BeanB">
  <property name="beanA" ref="beanA"/>
</bean>

Spring 啟動後,讀取如上的配置檔案,會按順序先例項化 A,但是建立的時候又發現它依賴了 B,接著就去例項化 B ,同樣又發現它依賴了 A ,這尼瑪咋整?無限迴圈呀

Spring “肯定”不會讓這種事情發生的,如前言我們說的 Spring 例項化物件分兩步,第一步會先建立一個原始物件,只是沒有設定屬性,可以理解為"半成品"—— 官方叫 A 物件的早期引用(EarlyBeanReference),所以當例項化 B 的時候發現依賴了 A, B 就會把這個“半成品”設定進去先完成例項化,既然 B 完成了例項化,所以 A 就可以獲得 B 的引用,也完成例項化了,這其實就是 Spring 解決迴圈依賴的思想。

有點懵逼

不理解沒關係,先有個大概的印象,然後我們從原始碼來看下 Spring 具體是怎麼解決的。

原始碼解毒

程式碼版本:5.0.16.RELEASE

在 Spring IOC 容器讀取 Bean 配置建立 Bean 例項之前, 必須對它進行例項化。只有在容器例項化後,才可以從 IOC 容器裡獲取 Bean 例項並使用,迴圈依賴問題也就是發生在例項化 Bean 的過程中的,所以我們先回顧下獲取 Bean 的過程。

獲取 Bean 流程

Spring IOC 容器中獲取 bean 例項的簡化版流程如下(排除了各種包裝和檢查的過程)

大概的流程順序(可以結合著原始碼看下,我就不貼了,貼太多的話,嘔~嘔嘔,想吐):

  1. 流程從 getBean 方法開始,getBean 是個空殼方法,所有邏輯直接到 doGetBean 方法中
  2. transformedBeanName 將 name 轉換為真正的 beanName(name 可能是 FactoryBean 以 & 字元開頭或者有別名的情況,所以需要轉化下)
  3. 然後通過 getSingleton(beanName) 方法嘗試從快取中查詢是不是有該例項 sharedInstance(單例在 Spring 的同一容器只會被建立一次,後續再獲取 bean,就直接從快取獲取即可)
  4. 如果有的話,sharedInstance 可能是完全例項化好的 bean,也可能是一個原始的 bean,所以再經 getObjectForBeanInstance 處理即可返回
  5. 當然 sharedInstance 也可能是 null,這時候就會執行建立 bean 的邏輯,將結果返回

第三步的時候我們提到了一個快取的概念,這個就是 Spring 為了解決單例的迴圈依賴問題而設計的 三級快取

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

這三級快取的作用分別是:

  • singletonObjects:完成初始化的單例物件的 cache,這裡的 bean 經歷過 例項化->屬性填充->初始化 以及各種後置處理(一級快取)

  • earlySingletonObjects:存放原始的 bean 物件(完成例項化但是尚未填充屬性和初始化),僅僅能作為指標提前曝光,被其他 bean 所引用,用於解決迴圈依賴的 (二級快取)

  • singletonFactories:在 bean 例項化完之後,屬性填充以及初始化之前,如果允許提前曝光,Spring 會將例項化後的 bean 提前曝光,也就是把該 bean 轉換成 beanFactory 並加入到 singletonFactories(三級快取)

我們首先從快取中試著獲取 bean,就是從這三級快取中查詢

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從 singletonObjects 獲取例項,singletonObjects 中的例項都是準備好的 bean 例項,可以直接使用
    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation() 判斷當前單例bean是否正在建立中
    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) {
                    // 三級快取有的話,就把他移動到二級快取,.getObject() 後續會講到
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

如果快取沒有的話,我們就要建立了,接著我們以單例物件為例,再看下建立 bean 的邏輯(大括號表示內部類呼叫方法):

  1. 建立 bean 從以下程式碼開始,一個匿名內部類方法引數(總覺得 Lambda 的方式可讀性不如內部類好理解)

    if (mbd.isSingleton()) {
        sharedInstance = getSingleton(beanName, () -> {
            try {
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                destroySingleton(beanName);
                throw ex;
            }
        });
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    

    getSingleton() 方法內部主要有兩個方法

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        // 建立 singletonObject
    	singletonObject = singletonFactory.getObject();
        // 將 singletonObject 放入快取
        addSingleton(beanName, singletonObject);
    }
    
  2. getObject() 匿名內部類的實現真正呼叫的又是 createBean(beanName, mbd, args)

  3. 往裡走,主要的實現邏輯在 doCreateBean方法,先通過 createBeanInstance 建立一個原始 bean 物件

  4. 接著 addSingletonFactory 新增 bean 工廠物件到 singletonFactories 快取(三級快取)

  5. 通過 populateBean 方法向原始 bean 物件中填充屬性,並解析依賴,假設這時候建立 A 之後填充屬性時發現依賴 B,然後建立依賴物件 B 的時候又發現依賴 A,還是同樣的流程,又去 getBean(A),這個時候三級快取已經有了 beanA 的“半成品”,這時就可以把 A 物件的原始引用注入 B 物件(並將其移動到二級快取)來解決迴圈依賴問題。這時候 getObject() 方法就算執行結束了,返回完全例項化的 bean

  6. 最後呼叫 addSingleton 把完全例項化好的 bean 物件放入 singletonObjects 快取(一級快取)中,打完收工

Spring 解決迴圈依賴

建議搭配著“原始碼”看下邊的邏輯圖,更好下飯

流程其實上邊都已經說過了,結合著上圖我們再看下具體細節,用大白話再捋一捋:

  1. Spring 建立 bean 主要分為兩個步驟,建立原始 bean 物件,接著去填充物件屬性和初始化
  2. 每次建立 bean 之前,我們都會從快取中查下有沒有該 bean,因為是單例,只能有一個
  3. 當我們建立 beanA 的原始物件後,並把它放到三級快取中,接下來就該填充物件屬性了,這時候發現依賴了 beanB,接著就又去建立 beanB,同樣的流程,建立完 beanB 填充屬性時又發現它依賴了 beanA,又是同樣的流程,不同的是,這時候可以在三級快取中查到剛放進去的原始物件 beanA,所以不需要繼續建立,用它注入 beanB,完成 beanB 的建立
  4. 既然 beanB 建立好了,所以 beanA 就可以完成填充屬性的步驟了,接著執行剩下的邏輯,閉環完成

這就是單例模式下 Spring 解決迴圈依賴的流程了。

但是這個地方,不管是誰看原始碼都會有個小疑惑,為什麼需要三級快取呢,我趕腳二級他也夠了呀

革命尚未成功,同志仍需努力

跟原始碼的時候,發現在建立 beanB 需要引用 beanA 這個“半成品”的時候,就會觸發"前期引用",即如下程式碼:

ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
    // 三級快取有的話,就把他移動到二級快取
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}

singletonFactory.getObject() 是一個介面方法,這裡具體的實現方法在

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;
                // 這麼一大段就這句話是核心,也就是當bean要進行提前曝光時,
                // 給一個機會,通過重寫後置處理器的getEarlyBeanReference方法,來自定義操作bean
                // 值得注意的是,如果提前曝光了,但是沒有被提前引用,則該後置處理器並不生效!!!
                // 這也正式三級快取存在的意義,否則二級快取就可以解決迴圈依賴的問題
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

這個方法就是 Spring 為什麼使用三級快取,而不是二級快取的原因,它的目的是為了後置處理,如果沒有 AOP 後置處理,就不會走進 if 語句,直接返回了 exposedObject ,相當於啥都沒幹,二級快取就夠用了。

所以又得出結論,這個三級快取應該和 AOP 有關係,繼續。

在 Spring 的原始碼中getEarlyBeanReferenceSmartInstantiationAwareBeanPostProcessor 介面的預設方法,真正實現這個方法的只有AbstractAutoProxyCreator 這個類,用於提前曝光的 AOP 代理。

@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   // 對bean進行提前Spring AOP代理
   return wrapIfNecessary(bean, beanName, cacheKey);
}

這麼說有點幹,來個小 demo 吧,我們都知道 Spring AOP、事務等都是通過代理物件來實現的,而事務的代理物件是由自動代理建立器來自動完成的。也就是說 Spring 最終給我們放進容器裡面的是一個代理物件,而非原始物件,假設我們有如下一段業務程式碼:

@Service
public class HelloServiceImpl implements HelloService {
   @Autowired
   private HelloService helloService;

   @Override
   @Transactional
   public Object hello() {
      return "Hello JavaKeeper";
   }
}

Service 類使用到了事務,所以最終會生成一個 JDK 動態代理物件 Proxy。剛好它又存在自己引用自己的迴圈依賴,完美符合我們的場景需求。

我們再自定義一個後置處理,來看下效果:

@Component
public class HelloProcessor implements SmartInstantiationAwareBeanPostProcessor {

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		System.out.println("提前曝光了:"+beanName);
		return bean;
	}
}

可以看到,呼叫方法棧中有我們自己實現的 HelloProcessor,說明這個 bean 會通過 AOP 代理處理。

再從原始碼看下這個自己迴圈自己的 bean 的建立流程:

protected Object doCreateBean( ... ){
	...
	
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    // 需要提前暴露(支援迴圈依賴),就註冊一個ObjectFactory到三級快取
	if (earlySingletonExposure) { 
        // 新增 bean 工廠物件到 singletonFactories 快取中,並獲取原始物件的早期引用
		//匿名內部方法 getEarlyBeanReference 就是後置處理器	
		// SmartInstantiationAwareBeanPostProcessor 的一個方法,
		// 它的功效為:保證自己被迴圈依賴的時候,即使被別的Bean @Autowire進去的也是代理物件
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	// 此處注意:如果此處自己被迴圈依賴了  那它會走上面的getEarlyBeanReference,從而建立一個代理物件從		三級快取轉移到二級快取裡
	// 注意此時候物件還在二級快取裡,並沒有在一級快取。並且此時後續的這兩步操作還是用的 exposedObject,它仍舊是原始物件~~~
	populateBean(beanName, mbd, instanceWrapper);
	exposedObject = initializeBean(beanName, exposedObject, mbd);

	// 因為事務的AOP自動代理建立器在getEarlyBeanReference 建立代理後,initializeBean 就不會再重複建立了,二選一的)
    	
	// 所以經過這兩大步後,exposedObject 還是原始物件,通過 getEarlyBeanReference 建立的代理物件還在三級快取呢
	
	...
	
	// 迴圈依賴校驗
	if (earlySingletonExposure) {
        // 注意此處第二個引數傳的false,表示不去三級快取裡再去呼叫一次getObject()方法了~~~,此時代理物件還在二級快取,所以這裡拿出來的就是個 代理物件
		// 最後賦值給exposedObject  然後return出去,進而最終被addSingleton()新增進一級快取裡面去  
		// 這樣就保證了我們容器裡 最終實際上是代理物件,而非原始物件~~~~~
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
			if (exposedObject == bean) { 
				exposedObject = earlySingletonReference;
			}
		}
		...
	}
	
}

自我解惑:

問:還是不太懂,為什麼這麼設計呢,即使有代理,在二級快取代理也可以吧 | 為什麼要使用三級快取呢?

我們再來看下相關程式碼,假設我們現在是二級快取架構,建立 A 的時候,我們不知道有沒有迴圈依賴,所以放入二級快取提前暴露,接著建立 B,也是放入二級快取,這時候發現又迴圈依賴了 A,就去二級快取找,是有,但是如果此時還有 AOP 代理呢,我們要的是代理物件可不是原始物件,這怎麼辦,只能改邏輯,在第一步的時候,不管3721,所有 Bean 統統去完成 AOP 代理,如果是這樣的話,就不需要三級快取了,但是這樣不僅沒有必要,而且違背了 Spring 在結合 AOP 跟 Bean 的生命週期的設計。

所以 Spring “多此一舉”的將例項先封裝到 ObjectFactory 中(三級快取),主要關鍵點在 getObject() 方法並非直接返回例項,而是對例項又使用 SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference 方法對 bean 進行處理,也就是說,當 Spring 中存在該後置處理器,所有的單例 bean 在例項化後都會被進行提前曝光到三級快取中,但是並不是所有的 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) {
             // 三級快取獲取,key=beanName value=objectFactory,objectFactory中儲存					//getObject()方法用於獲取提前曝光的例項
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 三級快取有的話,就把他移動到二級快取
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}


boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isDebugEnabled()) {
      logger.debug("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   // 新增 bean 工廠物件到 singletonFactories 快取中,並獲取原始物件的早期引用
   //匿名內部方法 getEarlyBeanReference 就是後置處理器
   // SmartInstantiationAwareBeanPostProcessor 的一個方法,
   // 它的功效為:保證自己被迴圈依賴的時候,即使被別的Bean @Autowire進去的也是代理物件~~~~  AOP自動代理建立器此方法裡會建立的代理物件~~~
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
再問:AOP 代理物件提前放入了三級快取,沒有經過屬性填充和初始化,這個代理又是如何保證依賴屬性的注入的呢?

這個又涉及到了 Spring 中動態代理的實現,不管是cglib代理還是jdk動態代理生成的代理類,代理時,會將目標物件 target 儲存在最後生成的代理 $proxy 中,當呼叫 $proxy 方法時會回撥 h.invoke,而 h.invoke 又會回撥目標物件 target 的原始方法。所有,其實在 AOP 動態代理時,原始 bean 已經被儲存在 提前曝光代理中了,之後 原始 bean 繼續完成屬性填充初始化操作。因為 AOP 代理$proxy 中儲存著 traget 也就是是 原始bean 的引用,因此後續 原始bean 的完善,也就相當於Spring AOP中的 target 的完善,這樣就保證了 AOP 的屬性填充初始化了!

非單例迴圈依賴

看完了單例模式的迴圈依賴,我們再看下非單例的情況,假設我們的配置檔案是這樣的:

<bean id="beanA" class="priv.starfish.BeanA" scope="prototype">
   <property name="beanB" ref="beanB"/>
</bean>

<bean id="beanB" class="priv.starfish.BeanB" scope="prototype">
   <property name="beanA" ref="beanA"/>
</bean>

啟動 Spring,結果如下:

Error creating bean with name 'beanA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB';

Error creating bean with name 'beanB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA';

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

對於 prototype 作用域的 bean,Spring 容器無法完成依賴注入,因為 Spring 容器不進行快取 prototype 作用域的 bean ,因此無法提前暴露一個建立中的bean 。

原因也挺好理解的,原型模式每次請求都會建立一個例項物件,即使加了快取,迴圈引用太多的話,就比較麻煩了就,所以 Spring 不支援這種方式,直接丟擲異常:

if (isPrototypeCurrentlyInCreation(beanName)) {
   throw new BeanCurrentlyInCreationException(beanName);
}

構造器迴圈依賴

上文我們講的是通過 Setter 方法注入的單例 bean 的迴圈依賴問題,用 Spring 的小夥伴也都知道,依賴注入的方式還有構造器注入、工廠方法注入的方式(很少使用),那如果構造器注入方式也有迴圈依賴,可以搞不?

我們再改下程式碼和配置檔案

public class BeanA {
   private BeanB beanB;
   public BeanA(BeanB beanB) {
      this.beanB = beanB;
   }
}

public class BeanB {
	private BeanA beanA;
	public BeanB(BeanA beanA) {
		this.beanA = beanA;
	}
}
<bean id="beanA" class="priv.starfish.BeanA">
<constructor-arg ref="beanB"/>
</bean>

<bean id="beanB" class="priv.starfish.BeanB">
<constructor-arg ref="beanA"/>
</bean>

執行結果,又是異常

看看官方給出的說法

Circular dependencies

If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.

For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.

One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.

Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).

大概意思是:

如果您主要使用構造器注入,迴圈依賴場景是無法解決的。建議你用 setter 注入方式代替構造器注入

其實也不是說只要是構造器注入就會有迴圈依賴問題,Spring 在建立 Bean 的時候預設是按照自然排序來進行建立的,我們暫且把先建立的 bean 叫主 bean,上文的 A 即主 bean,只要主 bean 注入依賴 bean 的方式是 setter 方式,依賴 bean 的注入方式無所謂,都可以解決,反之亦然

所以上文我們 AB 迴圈依賴問題,只要 A 的注入方式是 setter ,就不會有迴圈依賴問題。

面試官問:為什麼呢?

Spring 解決迴圈依賴依靠的是 Bean 的“中間態”這個概念,而這個中間態指的是已經例項化,但還沒初始化的狀態。例項化的過程又是通過構造器建立的,如果 A 還沒建立好出來,怎麼可能提前曝光,所以構造器的迴圈依賴無法解決,我一直認為應該先有雞才能有蛋

小總結 | 面試這麼答

B 中提前注入了一個沒有經過初始化的 A 型別物件不會有問題嗎?

雖然在建立 B 時會提前給 B 注入了一個還未初始化的 A 物件,但是在建立 A 的流程中一直使用的是注入到 B 中的 A 物件的引用,之後會根據這個引用對 A 進行初始化,所以這是沒有問題的。

Spring 是如何解決的迴圈依賴?

Spring 為了解決單例的迴圈依賴問題,使用了三級快取。其中一級快取為單例池(singletonObjects),二級快取為提前曝光物件(earlySingletonObjects),三級快取為提前曝光物件工廠(singletonFactories)。

假設A、B迴圈引用,例項化 A 的時候就將其放入三級快取中,接著填充屬性的時候,發現依賴了 B,同樣的流程也是例項化後放入三級快取,接著去填充屬性時又發現自己依賴 A,這時候從快取中查詢到早期暴露的 A,沒有 AOP 代理的話,直接將 A 的原始物件注入 B,完成 B 的初始化後,進行屬性填充和初始化,這時候 B 完成後,就去完成剩下的 A 的步驟,如果有 AOP 代理,就進行 AOP 處理獲取代理後的物件 A,注入 B,走剩下的流程。

為什麼要使用三級快取呢?二級快取能解決迴圈依賴嗎?

如果沒有 AOP 代理,二級快取可以解決問題,但是有 AOP 代理的情況下,只用二級快取就意味著所有 Bean 在例項化後就要完成 AOP 代理,這樣違背了 Spring 設計的原則,Spring 在設計之初就是通過 AnnotationAwareAspectJAutoProxyCreator 這個後置處理器來在 Bean 生命週期的最後一步來完成 AOP 代理,而不是在例項化後就立馬進行 AOP 代理。

參考與感謝:

《Spring 原始碼深度解析》- 郝佳著

https://developer.aliyun.com/article/766880

http://www.tianxiaobo.com/2018/06/08/Spring-IOC-容器原始碼分析-迴圈依賴的解決辦法

https://cloud.tencent.com/developer/article/1497692

https://blog.csdn.net/chaitoudaren/article/details/105060882

相關文章