該系列文章是本人在學習 Spring 的過程當中總結下來的,裏面涉及到相關源碼,可能對讀者不太友好,請結合個人源碼註釋 Spring 源碼分析 GitHub 地址 進行閱讀html
Spring 版本:5.1.14.RELEASEjava
開始閱讀這一系列文章以前,建議先查看《深刻了解 Spring IoC(面試題)》這一篇文章git
該系列其餘文章請查看:《死磕 Spring 之 IoC 篇 - 文章導讀》github
咱們先回到《Bean 的建立過程》中的「從緩存中獲取單例 Bean」小節,當加載一個 Bean 時,會嘗試從緩存(三個 Map)中獲取對象,若是未命中則進入後面建立 Bean 的過程。再來看到《Bean 的建立過程》中的「提早暴露當前 Bean」小節,當獲取到一個實例對象(還未設置屬性和初始化)後,會將這個「早期對象」放入前面的緩存中(第三個 Map),這裏暴露的對象實際是一個 ObjectFactory,能夠經過它獲取「早期對象」。這樣一來,在後面設置屬性的過程當中,若是須要依賴注入其餘 Bean,且存在循環依賴,那麼上面的緩存就避免了這個問題。接下來,將會分析 Spring 處理循環依賴的相關過程。面試
循環依賴,其實就是循環引用,就是兩個或者兩個以上的 Bean 互相引用對方,最終造成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A。spring
例如定義下面兩個對象:緩存
學生類源碼分析
public class Student { private Long id; private String name; @Autowired private ClassRoom classRoom; // 省略 getter、setter }
教室類性能
public class ClassRoom { private String name; @Autowired private Collection<Student> students; // 省略 getter、setter }
當加載 Student 這個對象時,須要注入一個 ClassRoom 對象,就須要去加載 ClassRoom 這個對象,此時又要去依賴注入全部的 Student 對象,這裏的 Student 和 ClassRoom 就存在循環依賴,那麼一直這樣循環下去,除非有終結條件。學習
Spring 只處理單例 Bean 的循環依賴,原型模式的 Bean 若是存在循環依賴直接拋出異常,單例 Bean 的循環依賴的場景有兩種:
對於構造器注入出現緩存依賴,Spring 是沒法解決的,由於當前 Bean 還未實例化,沒法提早暴露對象,因此只能拋出異常,接下來咱們分析的都是字段(或 Setter)注入出現循環依賴的處理
能夠先回到《Bean 的建立過程》中的「從緩存中獲取單例 Bean」小節,在獲取一個 Bean 過程當中,首先會從緩存中嘗試獲取對象,對應代碼段:
// AbstractBeanFactory#doGetBean(...) 方法 Object sharedInstance = getSingleton(beanName); // DefaultSingletonBeanRegistry.java public Object getSingleton(String beanName) { return getSingleton(beanName, true); } // DefaultSingletonBeanRegistry.java protected Object getSingleton(String beanName, boolean allowEarlyReference) { // <1> **【一級 Map】**從單例緩存 `singletonObjects` 中獲取 beanName 對應的 Bean Object singletonObject = this.singletonObjects.get(beanName); // <2> 若是**一級 Map**中不存在,且當前 beanName 正在建立 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // <2.1> 對 `singletonObjects` 加鎖 synchronized (this.singletonObjects) { // <2.2> **【二級 Map】**從 `earlySingletonObjects` 集合中獲取,裏面會保存從 **三級 Map** 獲取到的正在初始化的 Bean singletonObject = this.earlySingletonObjects.get(beanName); // <2.3> 若是**二級 Map** 中不存在,且容許提早建立 if (singletonObject == null && allowEarlyReference) { // <2.3.1> **【三級 Map】**從 `singletonFactories` 中獲取對應的 ObjectFactory 實現類 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 若是從**三級 Map** 中存在對應的對象,則進行下面的處理 if (singletonFactory != null) { // <2.3.2> 調用 ObjectFactory#getOject() 方法,獲取目標 Bean 對象(早期半成品) singletonObject = singletonFactory.getObject(); // <2.3.3> 將目標對象放入**二級 Map** this.earlySingletonObjects.put(beanName, singletonObject); // <2.3.4> 從**三級 Map**移除 `beanName` this.singletonFactories.remove(beanName); } } } } // <3> 返回從緩存中獲取的對象 return singletonObject; }
這裏的緩存指的就是上面三個 Map 對象:
singletonObjects
(一級 Map):裏面保存了全部已經初始化好的單例 Bean,也就是會保存 Spring IoC 容器中全部單例的 Spring BeanearlySingletonObjects
(二級 Map),裏面會保存從 三級 Map 獲取到的正在初始化的 BeansingletonFactories
(三級 Map),裏面保存了正在初始化的 Bean 對應的 ObjectFactory 實現類,調用其 getObject() 方法返回正在初始化的 Bean 對象(僅實例化還沒徹底初始化好)過程以下:
singletonObjects
中獲取 beanName 對應的 BeansingletonObjects
加鎖earlySingletonObjects
集合中獲取,裏面會保存從 三級 Map 獲取到的正在初始化的 BeansingletonFactories
中獲取對應的 ObjectFactory 實現類,若是從三級 Map 中存在對應的對象,則進行下面的處理回到《Bean 的建立過程》中的「提早暴露當前 Bean」小節,在獲取到實例對象後,若是是單例模式,則提早暴露這個實例對象,對應代碼段:
// AbstractAutowireCapableBeanFactory#doCreateBean(...) 方法 // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // <3> 提早暴露這個 `bean`,若是能夠的話,目的是解決單例模式 Bean 的循環依賴注入 // <3.1> 判斷是否能夠提早暴露 boolean earlySingletonExposure = (mbd.isSingleton() // 單例模式 && this.allowCircularReferences // 容許循環依賴,默認爲 true && isSingletonCurrentlyInCreation(beanName)); // 當前單例 bean 正在被建立,在前面已經標記過 if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } /** * <3.2> * 建立一個 ObjectFactory 實現類,用於返回當前正在被建立的 `bean`,提早暴露,保存在 `singletonFactories` (**三級 Map**)緩存中 * * 能夠回到前面的 {@link AbstractBeanFactory#doGetBean#getSingleton(String)} 方法 * 加載 Bean 的過程會先從緩存中獲取單例 Bean,能夠避免單例模式 Bean 循環依賴注入的問題 */ addSingletonFactory(beanName, // ObjectFactory 實現類 () -> getEarlyBeanReference(beanName, mbd, bean)); }
若是是單例模式、容許循環依賴(默認爲 true)、當前單例 Bean 正在被建立(前面已經標記過),則提早暴露
這裏會先經過 Lambda 表達式建立一個 ObjectFactory 實現類,以下:
// AbstractAutowireCapableBeanFactory.java protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() // RootBeanDefinition 不是用戶定義的(由 Spring 解析出來的) && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
入參 bean
爲當前 Bean 的實例對象(未初始化),這個實現類容許經過 SmartInstantiationAwareBeanPostProcessor 對這個提早暴露的對象進行處理,最終會返回這個提早暴露的對象。注意,這裏也能夠返回一個代理對象。
有了這個 ObjectFactory 實現類後,就須要往緩存中存放了,以下:
// DefaultSingletonBeanRegistry.java 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); } } }
能夠看到會將這個 ObjectFactory 往 singletonFactories
(三級 Map)中存放,到這裏對於 Spring 對單例 Bean 循環依賴的處理是否是就很是清晰了
在徹底初始化好一個單例 Bean 後,會緩存起來,以下:
// DefaultSingletonBeanRegistry.java protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
往 singletonObjects
(一級 Map)存放當前單例 Bean,同時從 singletonFactories
(三級 Map)和 earlySingletonObjects
(二級 Map)中移除
Spring 只處理單例 Bean 的字段(或 Setter)注入出現循環依賴,對於構造器注入出現的循環依賴會直接拋出異常。還有就是若是是經過 denpends-on
配置的依賴出現了循環,也會拋出異常,因此我以爲這裏的「循環依賴」換作「循環依賴注入」是否是更合適一點
Spring 處理循環依賴的解決方案以下:
singletonFactories
(三級 Map)中singletonFactories
(三級 Map)中對應的 ObjectFactory#getObject() 獲取這個早期對象,避免再次初始化問題一:爲何須要上面的 二級 Map ?
由於經過 三級 Map獲取 Bean 會有相關 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 的處理,避免重複處理,處理後返回的多是一個代理對象
例如在循環依賴中一個 Bean 可能被多個 Bean 依賴, A -> B(也依賴 A) -> C -> A,當你獲取 A 這個 Bean 時,後續 B 和 C 都要注入 A,沒有上面的 二級 Map的話,三級 Map 保存的 ObjectFactory 實現類會被調用兩次,會重複處理,可能出現問題,這樣作在性能上也有所提高
問題二:爲何不直接調用這個 ObjectFactory#getObject() 方法放入 二級Map中,而須要 三級 Map?
對於沒有不涉及到 AOP 的 Bean 確實能夠不須要
singletonFactories
(三級 Map),可是 Spring AOP 就是 Spring 體系中的一員,若是沒有singletonFactories
(三級 Map),意味着 Bean 在實例化後就要完成 AOP 代理,這樣違背了 Spring 的設計原則。Spring 是經過AnnotationAwareAspectJAutoProxyCreator
這個後置處理器在徹底建立好 Bean 後來完成 AOP 代理,而不是在實例化後就立馬進行 AOP 代理。若是出現了循環依賴,那沒有辦法,只有給 Bean 先建立代理對象,可是在沒有出現循環依賴的狀況下,設計之初就是讓 Bean 在徹底建立好後才完成 AOP 代理。