写在前面的话
正逢立冬,北风潜入悄无声,未品浓秋已立冬。
寒潮来袭,气温骤降,当下室外温度为7°,成都这一轮的新冠也扰人不得安宁,致敬在抗疫一线工作的英雄们。
什么是循环依赖?
循环依赖就是循环引用,就是两个或者多个bean相互之间的持有对方,
比如A引用B,B引用C,而C又引用A,这样它们就最终反映为一个环。
注意这里不是循环调用,循环调用是方法之间的环调用,
循环调用是无法解决的,除非有终结条件,否则就是死循环,最终会导致内存溢出错误。
spring如何解决依赖循环
先说分类,spring的循环依赖分为:构造器循环依赖和setter循环依赖。
结论是:构造器循环依赖没有办法解决,spring会直接抛错。
setter中的bean循环依赖解决必须使单例模式的,否则也是无法解决的
下面详细说一下各种的特点和具体表现
1.构造器循环依赖
表示通过构造器注入构成的循环依赖,无法解决,抛出BeanCurrentlyInCreationException异常来表示发生了循环依赖。
<bean id="A" class="com.bean.A">
<constructor-arg indes="0" ref="B"/>
</bean>
<bean id="B" class="com.bean.B">
<constructor-arg indes="0" ref="C"/>
</bean>
<bean id="C" class="com.bean.C">
<constructor-arg indes="0" ref="B"/>
</bean>
举个例子:
假设通过要创建A类的时候,构造器需要B类,这是就需要去创建B类,但是在创建B的时候发现B的构造器需要C类,
这时就需要去创建C,但是不巧的是C的构造器也需要A类,那么就形成了一个环,没有办法创建。
Spring有个机制是,会将每一个正在创建的bean标识符放在“当前创建bean池”中,创建过程中会一直保持,创建完毕就会将bean标识符从池子中清除掉。
如果创建过程中发现自己已经在该池中时就会抛出BeanCurrentlyInCreationException异常表示依赖循环。
2.setter循环依赖
定义为通过setter注入方式构成的循环依赖。
解决的原理是:
通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来解决setter循环依赖。
而且只能解决单例作用域的bean依赖循环。
通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bena
源代码如下:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
/**
*添加给定的单例工厂来构建指定的单例
*如果有必要。
*/
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);
}
}
}
/**
* 获取用于早期访问指定bean的引用,
* 通常用于解析循环引用。
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
我们举一个列子:
<bean id="A" class="com.bean.A">
<property name="B" ref="B"/>
</bean>
<bean id="B" class="com.bean.B">
<property name="C" ref="C"/>
</bean>
<bean id="C" class="com.bean.C">
<property name="A" ref="A"/>
</bean>
默认是“singleton”作用域。
需求还是创建A,B,C三个类
1.Spring容器创建单例“A”bean,首先会根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露创建中的bean,并将“A”标识符放到“当前创建bean池”,然后进行setter方式注入“B”。
2.Spring容器创建单例“B”bean,首先会根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露创建中的bean,并将“B”标识符放到“当前创建bean池”,然后进行setter方式注入“C”。
3.Spring容器创建单例“C”bean,首先会根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露创建中的bean,并将“C”标识符放到“当前创建bean池”,然后进行setter方式注入“C”。
这里在注入A的时候由于提前暴露了“ObjectFactory”工厂,从而可以使用工厂返回提前暴露一个创建中的bean,完成“C”的注入。
4.最后依赖注入“B”和“A”,完成setter注入。
3.prototype范围的依赖处理
结论:对于“prototype”作用域的bean,Spring容器无法完成依赖注入,
因为Spring容器规定不会进行缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean。
配置示例:
<bean id="A" class="com.bean.A" scope="prototype">
<property name="B" ref="B"/>
</bean>
<bean id="B" class="com.bean.B" scope="prototype">
<property name="C" ref="C"/>
</bean>
<bean id="C" class="com.bean.C" scope="prototype">
<property name="A" ref="A"/>
</bean>
注意: 问题: 那么如果可以控制bean之前能否循环引用吗?
回答:可以的。
对“singleton”作用域的bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用。
该属性表示是否允许bean之间的循环引用并自动尝试解析它们,默认是true。
设置为false时可在遇到循环引用时引发异常,完全不允许循环引用。
写在后面的话
我们初步介绍了一下Spring循环依赖的分类和解决原理,也引出了提前暴露bean的工厂,接下来我们会从源码层面详细介绍bean工厂和3级缓存。