Spring解決bean之間的循環依賴


轉自鏈接:https://blog.csdn.net/lyc_liyanchao/article/details/83099675
通過前幾節的分析,已經成功將bean實例化,但是大家一定要將bean的實例化和完成bean的創建區分開,bean的實例化僅僅是獲得了bean的實例,該bean仍在繼續創建之中,之后在該bean實例的基礎之上,還要做很多額外的操作,例如bean的屬性填充、處理器的應用、bean的循環依賴解決等,今天我們就來分析下Spring是如何解決bean之間的循環依賴。

當ClassA引用ClassB,ClassB又引用ClassA,那么兩個類之間就會形成一個閉環,導致循環依賴的出現。大家只需記住一點,Spring只能解決單例模式下的Setter循環依賴。

1.測試用例
bean和xml 

 1 package com.lyc.cn.v2.day01.cycle;
 2 
 3 /**
 4  * @author: LiYanChao
 5  * @create: 2018-10-16 23:59
 6  */
 7 public class ClassA {
 8     private ClassB classB;
 9 
10     public ClassB getClassB() {
11         return classB;
12     }
13 
14     public void setClassB(ClassB classB) {
15         this.classB = classB;
16     }
17 }

 

 1 package com.lyc.cn.v2.day01.cycle;
 2 
 3 /**
 4 * @author: LiYanChao
 5 * @create: 2018-10-16 23:59
 6 */
 7 public class ClassB {
 8 private ClassA classA;
 9 
10 public ClassA getClassA() {
11 return classA;
12 }
13 
14 public void setClassA(ClassA classA) {
15 this.classA = classA;
16 }
17 }

 

<!--循環依賴-->
<bean id="classA" class="com.lyc.cn.v2.day01.cycle.ClassA" scope="singleton">
<property name="classB" ref="classB"></property>
</bean>
<bean id="classB" class="com.lyc.cn.v2.day01.cycle.ClassB" scope="singleton">
<property name="classA" ref="classA"></property>
</bean>

結果

========測試方法開始=======

com.lyc.cn.v2.day01.cycle.ClassB@2d6a9952
com.lyc.cn.v2.day01.cycle.ClassA@22a71081

========測試方法結束=======

當scope="singleton"時結果是正常的,Spring為我們解決了bean之間的循環依賴,再將scope改為prototype,運行測試用例(摘取部分異常信息):

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:255)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:372)
... 40 more

從異常信息中可以看到Is there an unresolvable circular reference?,有循環依賴異常,這也證明了Spring是不能解決prototype作用域的bean之間的循環依賴的。

下面我們從源碼角度去分析,Spring是如何解決bean之間的循環依賴問題的。

  1 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
  2 
  3     // Instantiate the bean.
  4     // ① 實例化bean
  5     BeanWrapper instanceWrapper = null;
  6     // 注意factoryBeanInstanceCache是ConcurrentMap,remove方法會返回刪除的鍵值(如果不存在返回null)
  7     if (mbd.isSingleton()) {
  8         instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  9     }
 10     // 如果factoryBeanInstanceCache沒有緩存對應的BeanWrapper,則重新創建bean實例
 11     if (instanceWrapper == null) {
 12         instanceWrapper = createBeanInstance(beanName, mbd, args);
 13     }
 14     final Object bean = instanceWrapper.getWrappedInstance();
 15     Class<?> beanType = instanceWrapper.getWrappedClass();
 16     if (beanType != NullBean.class) {
 17         mbd.resolvedTargetType = beanType;
 18     }
 19 
 20     // Allow post-processors to modify the merged bean definition.
 21     // ② 允許MergedBeanDefinitionPostProcessor后處理器修改已合並的bean定義。
 22     synchronized (mbd.postProcessingLock) {
 23         if (!mbd.postProcessed) {
 24             try {
 25                 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
 26             }
 27             catch (Throwable ex) {
 28                 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex);
 29             }
 30             mbd.postProcessed = true;
 31         }
 32     }
 33 
 34     // Eagerly cache singletons to be able to resolve circular references
 35     // even when triggered by lifecycle interfaces like BeanFactoryAware.
 36     // ③ 提前緩存ObjectFactory以解決bean之間的循環依賴
 37     // mbd.isSingleton()->是否單例,Spring只解決單例bean的循環依賴問題
 38     // allowCircularReferences->是否允許循環依賴
 39     // isSingletonCurrentlyInCreation->該bean是否創建中
 40     boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
 41     if (earlySingletonExposure) {
 42         addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
 43     }
 44 
 45     // Initialize the bean instance.
 46     // ④ 初始化bean實例 這里大家要與第①步區分開,到這里bean已經完成了實例化,但是還沒有完成初始化的操作,例如bean的屬性填充
 47     Object exposedObject = bean;
 48     try {
 49         // 填充bean屬性
 50         populateBean(beanName, mbd, instanceWrapper);
 51         // 初始化bean
 52         exposedObject = initializeBean(beanName, exposedObject, mbd);
 53     }
 54     catch (Throwable ex) {
 55         if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
 56             throw (BeanCreationException) ex;
 57         }
 58         else {
 59             throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
 60         }
 61     }
 62 
 63     // ⑤ 循環依賴檢查
 64     if (earlySingletonExposure) {
 65         Object earlySingletonReference = getSingleton(beanName, false);
 66         if (earlySingletonReference != null) {
 67             if (exposedObject == bean) {
 68                 exposedObject = earlySingletonReference;
 69             }
 70             else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
 71                 String[] dependentBeans = getDependentBeans(beanName);
 72                 Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
 73                 for (String dependentBean : dependentBeans) {
 74                     if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
 75                         actualDependentBeans.add(dependentBean);
 76                     }
 77                 }
 78                 if (!actualDependentBeans.isEmpty()) {
 79                     throw new BeanCurrentlyInCreationException(beanName,
 80                             "Bean with name '" + beanName + "' has been injected into other beans [" +
 81                             StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
 82                             "] in its raw version as part of a circular reference, but has eventually been " +
 83                             "wrapped. This means that said other beans do not use the final version of the " +
 84                             "bean. This is often the result of over-eager type matching - consider using " +
 85                             "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
 86                 }
 87             }
 88         }
 89     }
 90 
 91     // Register bean as disposable.
 92     try {
 93         // ⑥ 根據bean的作用域注冊bean
 94         registerDisposableBeanIfNecessary(beanName, bean, mbd);
 95     }
 96     catch (BeanDefinitionValidationException ex) {
 97         throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
 98     }
 99     // ⑦ 返回bean實例
100     return exposedObject;
101 }

通過第一步已經獲得了bean的實例(第二步留在以后再講解),直接看第三步:提前緩存ObjectFactory以解決bean之間的循環依賴。

1.提前曝光對象
這里涉及到一個非常重要的接口ObjectFactory,該接口是一個函數式接口且只有一個方法:T getObject() throws BeansException;,該方法用於返回一個bean的實例,此時的bean已經完成初始化,但是尚未完成創建。

如果當前的bean滿足條件,則將當前正在創建的bean和其ObjectFactory對象提前曝光,加入到正在創建bean池中。

 

1 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
2     synchronized (this.singletonObjects) {
3         if (!this.singletonObjects.containsKey(beanName)) {
4             this.singletonFactories.put(beanName, singletonFactory);
5             this.earlySingletonObjects.remove(beanName);
6             this.registeredSingletons.add(beanName);
7         }
8     }
9 }

2.循環依賴的解決
在完成bean的實例創建之后,還要填充bean的屬性,針對ClassA,其屬性是ClassB,如果要填充ClassA的屬性則勢必先要實例化ClassB,那么這里又涉及到一個概念,RuntimeBeanReference–>運行時引用。

打開BeanDefinitionValueResolver類的resolveValueIfNecessary方法。摘取代碼片段(該方法會在以后全部分析)

判斷RuntimeBeanReference屬性
 

1 // ① RuntimeBeanReference->運行時引用
2 //   例如BeanA依賴BeanB,那么在配置文件中有通過配置ref標簽進行引用的,在解析BeanDefinition的時候,是不會直接實例化BeanB的,那么這個引用就是RuntimeBeanReference
3 if (value instanceof RuntimeBeanReference) {
4     RuntimeBeanReference ref = (RuntimeBeanReference) value;
5     return resolveReference(argName, ref);
6 }


解析RuntimeBeanReference(運行時引用)
 

 1 private Object resolveReference(Object argName, RuntimeBeanReference ref) {
 2     try {
 3         // 1、解析引用beanName
 4         Object bean;
 5         String refName = ref.getBeanName();
 6         refName = String.valueOf(doEvaluate(refName));
 7         // 2、判斷引用bean是否屬於父BeanFactory
 8         if (ref.isToParent()) {
 9             if (this.beanFactory.getParentBeanFactory() == null) {
10                 throw new BeanCreationException(
11                         this.beanDefinition.getResourceDescription(), this.beanName,
12                         "Can't resolve reference to bean '" + refName +
13                         "' in parent factory: no parent factory available");
14             }
15             bean = this.beanFactory.getParentBeanFactory().getBean(refName);
16         }
17         // 3、從當前beanFactory獲取引用beanName實例
18         else {
19             bean = this.beanFactory.getBean(refName);
20             this.beanFactory.registerDependentBean(refName, this.beanName);
21         }
22         if (bean instanceof NullBean) {
23             bean = null;
24         }
25         return bean;
26     }
27     catch (BeansException ex) {
28         throw new BeanCreationException(
29                 this.beanDefinition.getResourceDescription(), this.beanName,
30                 "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
31     }
32 }

該過程很簡單,首先解析refBeanName,然后通過getBean方法獲取其實例,此時當前創建的bean是ClassA,引用bean是ClassB。

獲取到ClassB實例之后,又要填充ClassB的屬性,此時又會出現對RuntimeBeanReference的解析,即ClassA,再去獲取ClassA的實例,此時的ClassA的實例已經被提前曝光,會從緩存中獲取ClassA的實例。

 1 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
 2     // 1、從緩存中獲取bean
 3     Object singletonObject = this.singletonObjects.get(beanName);
 4     // 2、未能獲取到bean,但是允許對當前創建的單例的早期引用(解決循環引用)
 5     // isSingletonCurrentlyInCreation-->判斷指定的單例bean是否當前正在創建(Spring只解決單例bean的循環依賴問題)
 6     if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
 7         synchronized (this.singletonObjects) {
 8             // 從earlySingletonObjects獲取提前曝光的bean
 9             singletonObject = this.earlySingletonObjects.get(beanName);
10             // 未能獲取到提前曝光的bean且當前的bean允許被創建早期依賴
11             if (singletonObject == null && allowEarlyReference) {
12                 // 從緩存中獲取BeanFactory
13                 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
14                 if (singletonFactory != null) {
15                     // 通過getObject()方法獲取提前曝光的bean
16                     singletonObject = singletonFactory.getObject();
17                     // 將獲取到的singletonObject緩存至earlySingletonObjects
18                     this.earlySingletonObjects.put(beanName, singletonObject);
19                     // 從singletonFactories移除bean
20                     this.singletonFactories.remove(beanName);
21                 }
22             }
23         }
24     }
25     return singletonObject;
26 }

從singletonObjects中無法獲取到bean的實例,因為此時bean尚未完成全部創建,但是由於我們提前曝光了ObjectFactory,所以通過singletonObject = singletonFactory.getObject();是可以獲取到bean的實例的。這樣就解決了Spring的循環依賴問題。

3.總結
Spring只能解決Setter方法注入的單例bean之間的循環依賴
ClassA依賴ClassB,ClassB又依賴ClassA,形成依賴閉環。Spring在獲取ClassA的實例時,不等ClassA完成創建就將其曝光加入正在創建的bean緩存中。在解析ClassA的屬性時,又發現依賴於ClassB,再次去獲取ClassB,當解析ClassB的屬性時,又發現需要ClassA的屬性,但此時的ClassA已經被提前曝光加入了正在創建的bean的緩存中,則無需創建新的的ClassA的實例,直接從緩存中獲取即可。從而解決循環依賴問題。
————————————————
原文鏈接:https://blog.csdn.net/lyc_liyanchao/article/details/83099675


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM