什么是循环依赖
循环依赖就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如 CircleA 引用 C ircleB , CircleB 引用 CircleC, CircleC 引用 CircleA,则它们最终反映为一个环。 此处不是循环调用,循环调用是方法之间的环调用。循环调用是无法解决的 除非有终结条件 ,否则就是死循环,最终导致内存溢出错误。
Spring 如何解决循环依赖
Spring容器循环依赖包括 构造器循环依赖 和 setter循环依赖,那 Spring容器如何解决循环依赖呢?
在 Spring中将循环依赖的处理分成了 3种情况。
-
构造器循环依赖
表示通过构造器注入构成的循环依赖, 此依赖是无法解决的 ,只能抛出 BeanCurrentlylnCreationException 异常表示循环依赖 。
如在创建 TestA类时, 构造器需要 TestB类,那将去创建 TestB, 在创建 TestB类时又发现需要 TestC类, 则又去创建 TestC, 最终在创建 TestC时发现又需要 TestA,从而形成一个环,没办法创建 。
Spring容器将每一个正在创建的 bean标识符放在一个“当前创建 bean池”中 , bean标识符在创建过程中将一直保持在这个池中,因此如果在创建 bean 过程中发现自己已经在“当前 创建 bean池” 里时,将抛出 BeanCurrentlylnCreationException异常表示循环依赖;而对于创建 完毕的 bean 将从“ 当前创建 bean 池”中 清除掉 。
我们通过一个直观的测试用例来进行分析 。
循环依赖配置文件—构造器注入
<bean id="testA" class="com.bean.TestA">
<constructor-arg index=”0” ref=”testB”/〉
</bean>
<bean id="testB" class="com.bean.TestB">
<constructor-arg Index="0" ref="testC"/〉
</bean>
<bean id="testC" class="com.bean.TestC">
<constructor-arg index="0" ref="testA"/〉
</bean>
创建测试用例。
@Test(expected=BeanCurrentlyinCreacionException.class)
publiC void testCircleByConstructor() throws Throwable
{
try {
new ClassPathXmlApplicationContext ("test.xml");
} catch(Exception e) {
//因为要在创建 testC 时抛出;
Throwable el = e.getCause().getCause().getCause();
throw el;
}
Setter循环依赖
对于 setter 注入造成的依赖是通过 Spring 容器 提前暴露刚完成构造器注入但未完成其他步骤(如 setter注入)的 bean来完成的,而且只能解决单例作用域的 bean 循环依赖 。 通过提前暴露一个单例工厂方法,从而使其他 bean 能引用到 该 bean,如下代码所示 :
//为避免后期循环依赖,可以在 bean 初始化完成前将创建实例的 ObjectFactory 加入工厂
addSingletonFactory(beanName, () ->
//对 bean再一次依赖引用,主要应用 SmartInstantiationAwareBeanPostProcessor
// 其中我们熟知的 AOP 就是在这里将 advice 动态织人 beaη中,若没有则直接返回bean ,不做任何处理
getEarlyBeanReference(beanName, mbd, bean));
具体步骤如下:
-
Spring 容器创建单例“testA” bean,首先根据元参构造器创建 bean,并暴露一个 “ObjectFactory”用于返回一个提前暴露的创建中的 bean,并将“ testA”标识符放到“当前 创建 bean池”, 然后进行 setter注入“testB”。
-
Spring 容器创建单例 “testB” bean,首先根据无参构造器创建 bean,并暴露一个 “ObjectFactory”用于返回一个提前暴露 的创建中的 bean,并将“ testB”标识符放到“当前 创建 bean 池”,然后进行 setter注入“ circle”。
-
Spring 容器创建单例“testC” bean,首先根据元参构造器创建 bean,并暴露一个 “ObjectFactory”用于返回 一个提前暴露的创建中的 bean,并将“testC”标识符放到“当前创建bean池”,然后进行 setter注入“testA”。 进行注入“testA”时由于提前暴露了“ObjectFactory” 工厂,从而使用它返回提前暴露的创建中的 bean。
-
最后在依赖注入 “testB”和“ testA”,完成 setter 注入 。
setter注入单例bean的循环依赖解决思路:
三级缓存
DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry{
//单例对象的缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//单例对象工厂的缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//提前曝光的单例对象的缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
//以上三个缓存就是所谓的三级缓存
//缓存正在创建的 bean
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//单例对象的缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
// 当前创建的bean不在缓存中且当前bean正在被创建
//创建A时,需要注入B ,先去创建B,B又需要创建A 此时去创建A A就不在缓存中且正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//创建bean,锁定缓存单利的对象集合
synchronized (this.singletonObjects) {
//创建前检查提早曝光的 单例bean中是否存在当前要创建的bean
//如果在提前曝光的单利bean缓存中,则说明当前bean正在被创建中,不处理
singletonObject = this.earlySingletonObjects.get(beanName);
//如果不存在,且允许提前引用(是否允许从singletonFactories中通过getObject拿到对象)
if (singletonObject == null && allowEarlyReference) {
//创建前再检查 单例bean的工厂缓存中是否存在当前bean的工厂缓存
//当某些方法需要提前初始化的时候则会调用 addSingletonFactory方法将对应的ObjectFactory 初始化策略缓存在singletonFactories
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
//如果当前单例bean的工厂已经被缓存则直接使用工厂 获取单例bean
if (singletonFactory != null) {
//调用预先设定的getObject()方法获取单例bean
singletonObject = singletonFactory.getObject();
//获取到后将之前的三级缓存提升为二级缓存
//添加到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//从三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
}
prototype 范围的依赖处理
//缓存正在创建中的 原型 bean--原型bean不支持循环依赖,如果循环依赖则直接报错就是基于该缓存实现的
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
//自己注入自己,或者自己注入的bean中又包含自己
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
对于“ prototype”作用域 bean, Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存“ prototype”作用域的 bean,因此无法提前暴露一个创建中的 bean示例如下:
-
创建配置文件
<bean id="testA" class="com.bean.CircleA" scope="prototype"> <property name=”tests” ref=”tests”/> </bean> <bean id="testB" class="com.bean.Circles" scope="prototype"> <property name="testC" ref="testC"/> </bean> <bean id="testC" class="com.bean.CircleC" scope="prototype"> <property name="testA" ref="testA"/> </bean>
-
创建测试用例
@Test(expected = BeanCurrentlyinCreationException.class ) public void testCircleBySetterAndPrototype() throws Throwable ( try { ClassPathXmlApplicationContext ctx =new ClassPathXmlApplicationContext( "testPrototype.xml"); System.out.println(ctx.getBean("testA")); }catch (Exception e) { Throwable el = e.getCause().getCause ().getCause(); throw el; } }
对于“singleton” 作用域 bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用。
网友评论