轉自鏈接: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