24--Spring解決bean之間的迴圈依賴
通過前幾節的分析,已經成功將bean例項化,但是大家一定要將bean的例項化和完成bean的建立區分開,bean的例項化僅僅是獲得了bean的例項,該bean仍在繼續建立之中,之後在該bean例項的基礎之上,還要做很多額外的操作,例如bean的屬性填充、處理器的應用、bean的迴圈依賴解決等,今天我們就來分析下Spring是如何解決bean之間的迴圈依賴。
當ClassA引用ClassB,ClassB又引用ClassA,那麼兩個類之間就會形成一個閉環,導致迴圈依賴的出現。大家只需記住一點,Spring只能解決單例模式下的Setter迴圈依賴。
1.測試用例
- bean和xml
package com.lyc.cn.v2.day01.cycle;
/**
* @author: LiYanChao
* @create: 2018-10-16 23:59
*/
public class ClassA {
private ClassB classB;
public ClassB getClassB() {
return classB;
}
public void setClassB(ClassB classB) {
this.classB = classB;
}
}
package com.lyc.cn.v2.day01.cycle;
/**
* @author: LiYanChao
* @create: 2018-10-16 23:59
*/
public class ClassB {
private ClassA classA;
public ClassA getClassA() {
return classA;
}
public void setClassA(ClassA classA) {
this.classA = classA;
}
}
<!--迴圈依賴-->
<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之間的迴圈依賴問題的。
引
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
// Instantiate the bean.
// ① 例項化bean
BeanWrapper instanceWrapper = null;
// 注意factoryBeanInstanceCache是ConcurrentMap,remove方法會返回刪除的鍵值(如果不存在返回null)
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
// 如果factoryBeanInstanceCache沒有快取對應的BeanWrapper,則重新建立bean例項
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// Allow post-processors to modify the merged bean definition.
// ② 允許MergedBeanDefinitionPostProcessor後處理器修改已合併的bean定義。
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// ③ 提前快取ObjectFactory以解決bean之間的迴圈依賴
// mbd.isSingleton()->是否單例,Spring只解決單例bean的迴圈依賴問題
// allowCircularReferences->是否允許迴圈依賴
// isSingletonCurrentlyInCreation->該bean是否建立中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
// ④ 初始化bean例項 這裡大家要與第①步區分開,到這裡bean已經完成了例項化,但是還沒有完成初始化的操作,例如bean的屬性填充
Object exposedObject = bean;
try {
// 填充bean屬性
populateBean(beanName, mbd, instanceWrapper);
// 初始化bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
// ⑤ 迴圈依賴檢查
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
// ⑥ 根據bean的作用域註冊bean
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
// ⑦ 返回bean例項
return exposedObject;
}
通過第一步已經獲得了bean的例項(第二步留在以後再講解),直接看第三步:提前快取ObjectFactory以解決bean之間的迴圈依賴。
1.提前曝光物件
這裡涉及到一個非常重要的介面ObjectFactory,該介面是一個函式式介面且只有一個方法:T getObject() throws BeansException;
,該方法用於返回一個bean的例項,此時的bean已經完成初始化,但是尚未完成建立。
如果當前的bean滿足條件,則將當前正在建立的bean和其ObjectFactory物件提前曝光,加入到正在建立bean池中。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
2.迴圈依賴的解決
在完成bean的例項建立之後,還要填充bean的屬性,針對ClassA,其屬性是ClassB,如果要填充ClassA的屬性則勢必先要例項化ClassB,那麼這裡又涉及到一個概念,RuntimeBeanReference-->執行時引用。
開啟BeanDefinitionValueResolver類的resolveValueIfNecessary方法。摘取程式碼片段(該方法會在以後全部分析)
- 判斷RuntimeBeanReference屬性
// ① RuntimeBeanReference->執行時引用
// 例如BeanA依賴BeanB,那麼在配置檔案中有通過配置ref標籤進行引用的,在解析BeanDefinition的時候,是不會直接例項化BeanB的,那麼這個引用就是RuntimeBeanReference
if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
return resolveReference(argName, ref);
}
- 解析RuntimeBeanReference(執行時引用)
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
// 1、解析引用beanName
Object bean;
String refName = ref.getBeanName();
refName = String.valueOf(doEvaluate(refName));
// 2、判斷引用bean是否屬於父BeanFactory
if (ref.isToParent()) {
if (this.beanFactory.getParentBeanFactory() == null) {
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Can't resolve reference to bean '" + refName +
"' in parent factory: no parent factory available");
}
bean = this.beanFactory.getParentBeanFactory().getBean(refName);
}
// 3、從當前beanFactory獲取引用beanName例項
else {
bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
}
if (bean instanceof NullBean) {
bean = null;
}
return bean;
}
catch (BeansException ex) {
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
}
}
該過程很簡單,首先解析refBeanName,然後通過getBean方法獲取其例項,此時當前建立的bean是ClassA,引用bean是ClassB。
獲取到ClassB例項之後,又要填充ClassB的屬性,此時又會出現對RuntimeBeanReference的解析,即ClassA,再去獲取ClassA的例項,此時的ClassA的例項已經被提前曝光,會從快取中獲取ClassA的例項。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1、從快取中獲取bean
Object singletonObject = this.singletonObjects.get(beanName);
// 2、未能獲取到bean,但是允許對當前建立的單例的早期引用(解決迴圈引用)
// isSingletonCurrentlyInCreation-->判斷指定的單例bean是否當前正在建立(Spring只解決單例bean的迴圈依賴問題)
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 從earlySingletonObjects獲取提前曝光的bean
singletonObject = this.earlySingletonObjects.get(beanName);
// 未能獲取到提前曝光的bean且當前的bean允許被建立早期依賴
if (singletonObject == null && allowEarlyReference) {
// 從快取中獲取BeanFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通過getObject()方法獲取提前曝光的bean
singletonObject = singletonFactory.getObject();
// 將獲取到的singletonObject快取至earlySingletonObjects
this.earlySingletonObjects.put(beanName, singletonObject);
// 從singletonFactories移除bean
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
從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的例項,直接從快取中獲取即可。從而解決迴圈依賴問題。
相關文章
- Spring框架是怎麼解決Bean之間的迴圈依賴的 (轉)Spring框架Bean
- Spring 中 bean 的迴圈依賴SpringBean
- 【spring原始碼系列】之【Bean的迴圈依賴】Spring原始碼Bean
- spring迴圈依賴解決過程&Bean的生命週期SpringBean
- spring解決迴圈依賴Spring
- 深談Spring如何解決Bean的迴圈依賴SpringBean
- 解決rpm包迴圈依賴
- Spring 迴圈依賴的三種方式(三級快取解決Set迴圈依賴問題)Spring快取
- Spring原始碼分析之迴圈依賴及解決方案Spring原始碼
- 死磕Spring之IoC篇 - 單例 Bean 的迴圈依賴處理Spring單例Bean
- 【Spring】Spring中的迴圈依賴及解決Spring
- 再探迴圈依賴 → Spring 是如何判定原型迴圈依賴和構造方法迴圈依賴的?Spring原型構造方法
- Spring如何解決迴圈依賴?Spring
- springboot bean的迴圈依賴實現 原始碼分析Spring BootBean原始碼
- go 中的迴圈依賴Go
- 探索 JavaScript 中的依賴管理及迴圈依賴JavaScript
- SpringIOC迴圈依賴Spring
- Spring迴圈依賴Spring
- 淺談迴圈依賴
- spring是如何解決迴圈依賴的?Spring
- Spring是如何解決迴圈依賴的Spring
- Spring中如何解決迴圈依賴Spring
- Spring學習:簡單實現一個依賴注入和迴圈依賴的解決Spring依賴注入
- saltstack對遞迴依賴條件(死迴圈依賴)的處理遞迴
- Spring中的迴圈依賴Spring
- spring: 我是如何解決迴圈依賴的?Spring
- Spring如何使用三級快取解決迴圈依賴Spring快取
- Spring IoC - 迴圈依賴Spring
- kmdjs和迴圈依賴JS
- 四探迴圈依賴 → 當迴圈依賴遇上 BeanPostProcessor,愛情可能就產生了!Bean
- Spring原始碼分析之IOC迴圈依賴Spring原始碼
- 一文詳解spring迴圈依賴Spring
- 3.1 spring5原始碼系列--迴圈依賴 之 手寫程式碼模擬spring迴圈依賴Spring原始碼
- 【Spring】快速理解迴圈依賴Spring
- Spring迴圈依賴+案例解析Spring
- 徹底理解Spring如何解決迴圈依賴Spring
- c++類迴圈依賴的問題C++
- 【Spring系列】- Spring迴圈依賴Spring