希望之光永遠向著目標清晰的人敞開。

1. 迴圈依賴概述

迴圈依賴通俗講就是迴圈引用,指兩個或兩個以上物件的bean相互引用對方,A依賴於B,B依賴於A,最終形成一個閉環。

Spring迴圈依賴的場景有兩種:

  • 構造器的迴圈依賴
  • field 屬性的迴圈依賴

    對於構造器的迴圈依賴,Spring 是無法解決,只能丟擲 BeanCurrentlyInCreationException 異常;對於field 屬性的迴圈依賴,Spring 只解決 scope 為 singleton 的迴圈依賴,對於scope 為 prototype 的 bean Spring 無法解決,直接丟擲 BeanCurrentlyInCreationException 異常。下面重點分析屬性依賴的情況。

2. 迴圈依賴執行流程



以上流程圖針對如下程式碼進行演示:

  1. @Component
  2. public class CircularRefA {
  3. public CircularRefA() {
  4. System.out.println("============CircularRefA()===========");
  5. }
  6. //這裡會觸發CircularRefB型別的getBean操作
  7. @Autowired
  8. private CircularRefB circularRefB;
  9. }
  1. @Component
  2. public class CircularRefB {
  3. public CircularRefB() {
  4. System.out.println("============CircularRefB()===========");
  5. }
  6. //又會觸發A的getBean操作
  7. @Autowired
  8. private CircularRefA circularRefA;
  9. }

step1: A類例項化執行,第一次三個快取中都沒有,會走doGetBean一路走到createBeanInstance,完成無參建構函式例項化,並在addSingletonFactory中設定三級快取;

step2:A類populateBean進行依賴注入,隨後觸發了B類屬性的getBean操作;

step3: B類與A類類似,第一次三個快取中也都沒有,無參建構函式例項化後,設定三級快取,將自己加入三級快取;

step4:B類populateBean進行依賴注入,這裡觸發了A類屬性的getBean操作;

step5: A類之前正在建立,此時已經是第二次進入,由於一級二級快取中都沒有,會從三級快取中獲取,並且允許 bean提前暴露則從三級快取中拿到物件工廠,從工廠中拿到物件成功後,升級到二級快取,並刪除三級快取;若以後有別的類引用的話就從二級快取中進行取;

step6:B類拿到了A的提前暴露例項注入到A類屬性中了,此時完成B類的例項化;

step7:A類之前依賴B類,B的例項化完成,進而促使A的例項化也完成,並且此時A的類B屬性已經有值,A類繼續走後續的afterSingletonCreateion與addSingleton方法,刪除正在建立快取中的例項,並將例項從二級快取移入以及快取,同時刪除二三級快取;

以上是A類例項化的全過程,下面會針對原始碼逐一分析。

3. 原始碼分析

首先建立A的例項,需要從A的getBean方法開始,到doGetBean,第一次優先從快取中取,進入getSingleton方法:

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. // 從一級快取singletonObjects中取
  3. Object singletonObject = this.singletonObjects.get(beanName);
  4. // 一級快取為空,且單例物件正在建立
  5. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  6. synchronized (this.singletonObjects) {
  7. // 從二級快取earlySingletonObjects中取
  8. singletonObject = this.earlySingletonObjects.get(beanName);
  9. // 如果二級快取也為空,且允許bean提前暴露
  10. if (singletonObject == null && allowEarlyReference) {
  11. // 從三級快取singletonFactories中取
  12. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  13. // 如果三級快取不為空
  14. if (singletonFactory != null) {
  15. // 呼叫getEarlyBeanReference方法提前暴露bean
  16. singletonObject = singletonFactory.getObject();
  17. // 提前暴露的bean放入二級快取,
  18. this.earlySingletonObjects.put(beanName, singletonObject);
  19. // 將提前暴露的bean從三級快取中刪除
  20. this.singletonFactories.remove(beanName);
  21. }
  22. }
  23. }
  24. }
  25. return singletonObject;
  26. }

這個方法主要是從三個快取中獲取,分別是:singletonObjectsearlySingletonObjectssingletonFactories,三者定義如下:

  1. /** Cache of singleton objects: bean name to bean instance. */
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  3. /** Cache of singleton factories: bean name to ObjectFactory. */
  4. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  5. /** Cache of early singleton objects: bean name to bean instance. */
  6. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

意義如下:

  • singletonObjects:單例物件的cache
  • singletonFactories : 單例物件工廠的cache
  • earlySingletonObjects :提前暴露的單例物件的Cache

解決迴圈依賴的關鍵是三個快取,其中一級快取singletonObjects存放完全例項化的物件,物件以及其依賴的屬性都有值;二級快取earlySingletonObjects存放半例項化的物件,相當於在記憶體開闢了空間,已完成建立,但是還未進行屬性賦值,可以提前暴露使用;三級快取singletonFactories為物件工廠,用來建立提前暴露的bean並放入二級快取中。

首次初始化A時,三個快取中都沒有物件,會進入如下getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法,該方法第二個引數是個函式式介面,當內部呼叫getObject方法時,會呼叫createBean方法:

  1. // 建立bean例項
  2. if (mbd.isSingleton()) {
  3. sharedInstance = getSingleton(beanName, () -> {
  4. try {
  5. return createBean(beanName, mbd, args);
  6. }
  7. catch (BeansException ex) {
  8. // Explicitly remove instance from singleton cache: It might have been put there
  9. // eagerly by the creation process, to allow for circular reference resolution.
  10. // Also remove any beans that received a temporary reference to the bean.
  11. destroySingleton(beanName);
  12. throw ex;
  13. }
  14. });
  15. ......

先進入getSingleton方法:

  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  2. Assert.notNull(beanName, "Bean name must not be null");
  3. synchronized (this.singletonObjects) {
  4. Object singletonObject = this.singletonObjects.get(beanName);
  5. if (singletonObject == null) {
  6. if (this.singletonsCurrentlyInDestruction) {
  7. throw new BeanCreationNotAllowedException(beanName,
  8. "Singleton bean creation not allowed while singletons of this factory are in destruction " +
  9. "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
  10. }
  11. if (logger.isDebugEnabled()) {
  12. logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
  13. }
  14. // bean單例建立前
  15. beforeSingletonCreation(beanName);
  16. boolean newSingleton = false;
  17. boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
  18. if (recordSuppressedExceptions) {
  19. this.suppressedExceptions = new LinkedHashSet<>();
  20. }
  21. try {
  22. // 呼叫createBean方法建立bean
  23. singletonObject = singletonFactory.getObject();
  24. newSingleton = true;
  25. }
  26. catch (IllegalStateException ex) {
  27. // Has the singleton object implicitly appeared in the meantime ->
  28. // if yes, proceed with it since the exception indicates that state.
  29. singletonObject = this.singletonObjects.get(beanName);
  30. if (singletonObject == null) {
  31. throw ex;
  32. }
  33. }
  34. catch (BeanCreationException ex) {
  35. if (recordSuppressedExceptions) {
  36. for (Exception suppressedException : this.suppressedExceptions) {
  37. ex.addRelatedCause(suppressedException);
  38. }
  39. }
  40. throw ex;
  41. }
  42. finally {
  43. if (recordSuppressedExceptions) {
  44. this.suppressedExceptions = null;
  45. }
  46. // 建立完成後要從正在例項化的bean集合singletonsCurrentlyInCreation中刪除該bean
  47. afterSingletonCreation(beanName);
  48. }
  49. if (newSingleton) {
  50. // bean加入快取
  51. addSingleton(beanName, singletonObject);
  52. }
  53. }
  54. return singletonObject;
  55. }
  56. }

該方法完成流程圖中的四個步驟:

step1:bean單例建立前,將beanName放入singletonsCurrentlyInCreation快取;

step2: singletonFactory.getObject()呼叫外面函式式介面中的createBean方法建立bean;

step3afterSingletonCreation方法將beanName從singletonsCurrentlyInCreation快取刪除,表示已建立完;

step4: 例項化後加入一級快取,二三級快取刪除。

以上1、3、4涉及程式碼如下:

  1. protected void beforeSingletonCreation(String beanName) {
  2. // 將正在建立的bean放入快取singletonsCurrentlyInCreation
  3. if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
  4. throw new BeanCurrentlyInCreationException(beanName);
  5. }
  6. }
  1. protected void afterSingletonCreation(String beanName) {
  2. // 正在建立中的快取容器singletonsCurrentlyInCreation清除剛剛建立的bean
  3. if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
  4. throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
  5. }
  6. }
  1. protected void addSingleton(String beanName, Object singletonObject) {
  2. synchronized (this.singletonObjects) {
  3. // 一級快取存放bean
  4. this.singletonObjects.put(beanName, singletonObject);
  5. // 三級快取移除bean
  6. this.singletonFactories.remove(beanName);
  7. // 二級快取移除bean
  8. this.earlySingletonObjects.remove(beanName);
  9. //
  10. this.registeredSingletons.add(beanName);
  11. }
  12. }

其中step2流程較長,當A第一次例項化時,走到建立無參建構函式例項化createBeanInstance,隨後會走addSingletonFactory方法:

  1. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  2. Assert.notNull(singletonFactory, "Singleton factory must not be null");
  3. synchronized (this.singletonObjects) {
  4. if (!this.singletonObjects.containsKey(beanName)) {
  5. this.singletonFactories.put(beanName, singletonFactory);
  6. this.earlySingletonObjects.remove(beanName);
  7. this.registeredSingletons.add(beanName);
  8. }
  9. }
  10. }

從這段程式碼我們可以看出singletonFactories這個三級快取才是解決迴圈依賴的關鍵,該程式碼在createBeanInstance方法之後,也就是說這個bean其實已經被創建出來了,但是它還不是很完美(沒有進行屬性填充和初始化),但是對於其他依賴它的物件而言已經足夠了(可以根據物件引用定位到堆中物件),能夠被認出來了,所以Spring在這個時候選擇將該物件提前曝光出來讓大家認識認識。當三級快取有值後,後面如果再次用到該bean的時候,會從三級快取中,並通過提前暴露,升級到二級快取中,到這裡我們發現三級快取singletonFactories和二級快取earlySingletonObjects中的值都有出處了,那一級快取在哪裡設定的呢?就是在A建立完,並把A依賴的屬性B也建立完後,B有依賴於A,再次進入A後,A直接從二級快取中獲取,從而促使B物件建立完,隨即A也就建立完成,A完成createBean後走上面的step4中的addSingletion方法,完成一級快取的設定。

4.總結

Spring在建立bean的時候並不是等它完全完成,而是在建立過程中將建立中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 快取中),這樣一旦下一個 bean 建立的時候需要依賴 bean ,則直接使用 ObjectFactory 的 getObject() 獲取了,故在快取中使用三級快取獲取到例項,並將例項升級到二級快取,供後續例項如需二次使用時,可直接從二級快取中取,待例項完全建立後,升級到一級快取,並清理二級三級快取,總而言之提前暴露三級快取,以及一二三級快取的綜合使用是解決迴圈依賴的關鍵,各級快取各司其職,又能夠相互呼應,spring的設計實在精妙,給我們自己設計專案提供了一種優秀的思考方式。