共计 6226 个字符,预计需要花费 16 分钟才能阅读完成。
引言
基于 Spring5+
什么是循环依赖?
循环依赖有几种?
Spring 可以解决哪几种,为什么不能解决这几种?
Spring 是如何判断存在循环依赖的?
什么是循环依赖?
什么是循环依赖?我们都知道 Spring 最大的作用就是来替我们管理 Bean 的,当然也包括 Bean 的创建以及整个生命周期,但是有这么一种情况,假设有三个类 A、B、C 需要交给 Spring 来管理,但 A 实例的创建需要先有 B 实例,而 B 实例的创建需要先有 C 实例,C 实例的创建需要先有 A 实例,这样三个类就自然形成了一个环状结构,如果用代码来表示,如下:
public class TestA {
TestB testB;
get;
set;
}
public class TestB {
TestC testC;
get;
set;
}
public class TestC {
TestA testA;
get;
set;
}
这样,三个类就彼此形成了一个环状,那么 Spring 是如何来处理这样的状况呢?
循环依赖有几种?
有三种情况:
基于构造方法的循环依赖
基于 setter 构造的循环依赖(网上也叫 field 属性依赖)
基于 prototype 范围的依赖
Spring 可以解决哪些循环依赖,为什么?
首先说一下结论:除了第二种 Spring 可以帮我们解决,其它两种都不能解决。我们知道 Spring 为我们完全实例化好一个 Bean 一定会经过一下三步:
createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象。
populateBean:填充属性,这一步主要是多 bean 的依赖属性进行填充。
initializeBean:调用默认的或者自定义的 init 方法。
循环依赖的产生定会发生在步骤 1 和 2 中,因为 1 是利用构造方法,2 是利用属性赋值。
基于构造方法的循环依赖
先说结论基于构造器的循环依赖 Spring 是无法解决的,是因为没有加入提前曝光的集合中,加入集合的条件是已经创建了 Bean 的包装对象,而构造注入的时候,并没有完成对象的创建,下面会有代码说明。
测试用例:
xml 文件:
<bean id=”testA” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopA”>
<constructor-arg index=”0″ ref=”testB”/>
</bean>
<bean id=”testB” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopB”>
<constructor-arg index=”0″ ref=”testC”/>
</bean>
<bean id=”testC” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopC”>
<constructor-arg index=”0″ ref=”testA”/>
</bean>
测试类:
/**
* description: 测试通过有参构造方式注入产生的循环依赖问题
* @author 70KG
* @date 2018/12/21
*/
public class Test02 {
@Test
public void m1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(“test02.xml”);
}
}
分析上面代码:
Spring 容器创建 testA 的 Bean 实例,首先去 ” 当前创建 Bean 池 ”,查找是否当前 Bean 正在创建,如果没发现,则继续准备其需要的构造器参数 testB,并将 testA 标识符放到 ” 当前创建 Bean 池 ”。
Spring 容器创建 testB 的 Bean 实例,首先去 ” 当前创建 Bean 池 ”,查找是否当前 Bean 正在创建,如果没发现,则继续准备其需要的构造器参数 testC,并将 testB 标识符放到 ” 当前创建 Bean 池 ”。
Spring 容器创建 testC 的 Bean 实例,首先去 ” 当前创建 Bean 池 ”,查找是否当前 Bean 正在创建,如果没发现,则继续准备其需要的构造器参数 testA,并将 testC 标识符放到 ” 当前创建 Bean 池 ”。
到此为止 Spring 容器要去创建 testA,但发现该 Bean 的标志符在 ” 当前创建 Bean 池 ” 中,表示了循环依赖,于是抛出 BeanCurrentlyInCreationException 异常。
其中 ” 当前创建 Bean 池 ” 就是一个 Set 集合,DefaultSingletonBeanRegistry 类中 beforeSingletonCreation 方法,代码如下:
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
然后我们来到创建 Bean 实例的地方:
AbstractAutowireCapableBeanFactory 类的 543 行,通过这个方法返回一个这个 Bean 的包装对象:
–> instanceWrapper = createBeanInstance(beanName, mbd, args);—-> 进入这个方法
–> AbstractAutowireCapableBeanFactory 类的 1129 行
// Need to determine the constructor…
// 需要确定构造函数,也就是说构造方法的循环依赖会在这儿 return
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// No special handling: simply use no-arg constructor.
// 无需特殊处理,仅使用无参构造即可,setter 的循环依赖会在这个地方 return
return instantiateBean(beanName, mbd);
在上面代码中返回 Bean 的包装对象下面紧接着才是将这个对象曝光,也就是加入到 SingletonFactory 集合中,所以构造方法的循环引用,Spring 是无法解决的,来到 AbstractAutowireCapableBeanFactory 的 574 行。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
基于 setter 构造的循环依赖
首先说结论:Spring 是可以为我们解决这样的依赖的,原理说白了就是用了缓存处理,也就是常说的提前曝光,为什么叫提前曝光呢?因为这个缓存中的 Bean 是一个还未进行赋值的 Bean,仅仅是一个引用而已。
xml 文件:
<bean id=”testA” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopA”>
<property name=”loopB” ref=”testB”/>
</bean>
<bean id=”testB” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopB”>
<property name=”loopC” ref=”testC”/>
</bean>
<bean id=”testC” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopC”>
<property name=”loopA” ref=”testA”/>
</bean>
测试类:
/**
* description: 通过 setter 注入产生的循环依赖问题
* @author 70KG
*/
public class Test03 {
@Test
public void m1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(“test03.xml”);
}
}
代码分析:
Spring 容器创建单例 ”loopA”,首先根据无参构造创建 Bean,并暴露到 Map(singletonFactories)中,并将 ”loopA” 标志符放到当前创建正在创建的 Bean 池 (singletonsCurrentlyInCreation) 中,然后进行 setter 注入 ”loopB”。
Spring 容器创建单例 ”loopB”,首先根据无参构造创建 Bean,并暴露到 Map(singletonFactories)中,并将 ”loopA” 标志符放到当前创建正在创建的 Bean 池 (singletonsCurrentlyInCreation) 中,然后进行 setter 注入 ”loopC”。
Spring 容器创建单例 ”loopC”,首先根据无参构造创建 Bean,并暴露到 Map(singletonFactories)中,并将 ”loopA” 标志符放到当前创建正在创建的 Bean 池 (singletonsCurrentlyInCreation) 中,然后进行 setter 注入 ”loopA”。在注入 ”loopA” 的时候,由于提前暴露在 singletonFactories 集合中了,利用它就可以取到 ”loopA” 正在创建的 Bean 对象。
最后依赖注入 ”testB”,”testA”,完成 setter 注入。
查看控制台输出日志:
// 正在创建 testA 对象
Creating shared instance of singleton bean ‘testA’
Creating instance of bean ‘testA’
// 在缓存早期引用,目的是防止循环引用问题
Eagerly caching bean ‘testA’ to allow for resolving potential circular references
Creating shared instance of singleton bean ‘testB’
Creating instance of bean ‘testB’
Eagerly caching bean ‘testB’ to allow for resolving potential circular references
Creating shared instance of singleton bean ‘testC’
Creating instance of bean ‘testC’
Eagerly caching bean ‘testC’ to allow for resolving potential circular references
// 在创建 testC 的时候会去缓存中拿原来存储的 testA,并返回,但此时的 testA 是一个不完全的对象,也就是尚未初始化
Returning eagerly cached instance of singleton bean ‘testA’ that is not fully initialized yet – a consequence of a circular reference
// 紧接着完成 C 的创建,顺便其它的也完成了
Finished creating instance of bean ‘testC’
Finished creating instance of bean ‘testB’
Finished creating instance of bean ‘testA’
Returning cached instance of singleton bean ‘testB’
Returning cached instance of singleton bean ‘testC’
基于 setter 的循环依赖利用了提前曝光机制,这一步的关键代码,在 AbstractAutowireCapableBeanFactory 的 574 行,代码如下:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
在加入 SingletonFactory 的前提是此 Bean 已经创建出来,才能够加入到这个 Map 集合中,也就是提前曝光,可以让别的 Bean 在初始化的时候从中拿到。否则是没有机会加入到 Map 中的。
基于 prototype 范围的依赖
首先说结论,对于多例情况下的循环依赖,是无法解决的,因为 Spring 容器不进行缓存,更无法提前暴露。
测试用例:
xml 文件:
<bean id=”testA” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopA” scope=”prototype”>
<property name=”loopB” ref=”testB”/>
</bean>
<bean id=”testB” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopB” scope=”prototype”>
<property name=”loopC” ref=”testC”/>
</bean>
<bean id=”testC” class=”com.nmys.story.springCore.loop_dependency.loop01.LoopC” scope=”prototype”>
<property name=”loopA” ref=”testA”/>
</bean>
测试类:
/**
* description: 通过 setter 注入产生的循环依赖问题
* @author 70KG
*/
public class Test03 {
@Test
public void m1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(“test03.xml”);
LoopA loopA = context.getBean(LoopA.class);
System.out.println(loopA);
}
}
会抛出 BeanCurrentlyInCreationException 异常。
Spring 是如何检测循环依赖
来到 AbstractBeanFactory 的 246 行,代码如下:
Object sharedInstance = getSingleton(beanName);
这一步是从缓存中获取以前创建的实例,如果发现存在,那么就存在循环依赖。
到此,全文完,自我感觉比其他的整理还算详细,如有疑问,请留言。