09 Bean的循环依赖问题
9.1 什么是 Bean 的循环依赖¶
A 对象中有 B 属性。B 对象中有 A 属性。这就是循环依赖。我依赖你,你也依赖我。 比如:丈夫类 Husband,妻子类 Wife。Husband 中有 Wife 的引用。Wife 中有 Husband 的引用。 ![[09.01-1.png]] ```java title:Husband package com.powernode.spring6.bean;
/** * @author 动力节点 * @version 1.0 * @className Husband * @since 1.0 */ public class Husband { private String name; private Wife wife; }
```java title:Wife
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className Wife
* @since 1.0
*/
public class Wife {
private String name;
private Husband husband;
}
9.2 singleton 下的 set 注入产生的循环依赖¶
我们来编写程序,测试一下在 singleton+setter 的模式下产生的循环依赖,Spring 是否能够解决?
```java title:Husband package com.powernode.spring6.bean;
/** * @author 动力节点 * @version 1.0 * @className Husband * @since 1.0 */ public class Husband { private String name; private Wife wife;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
// toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
java title:Wife
package com.powernode.spring6.bean;
/**
* @author 动力节点
* @version 1.0
* @className Wife
* @since 1.0
*/
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
// toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
```
```xml title:spring.xml
<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
java title:CircularDependencyTest
package com.powernode.spring6.test;
import com.powernode.spring6.bean.Husband;
import com.powernode.spring6.bean.Wife;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 动力节点
* @version 1.0
* @className CircularDependencyTest
* @since 1.0
*/
public class CircularDependencyTest {
@Test
public void testSingletonAndSet(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
System.out.println(husbandBean);
System.out.println(wifeBean);
}
}
```
执行结果:
![[09.02-1.png]]
通过测试得知:在 singleton + set 注入的情况下,循环依赖是没有问题的。Spring 可以解决这个问题。
9.3 prototype 下的 set 注入产生的循环依赖¶
我们再来测试一下:prototype+set 注入的方式下,循环依赖会不会出现问题?
```xml title:spring.xml
<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
<property name="name" value="翠花"/>
<property name="husband" ref="husbandBean"/>
</bean>
执行测试程序:发生了异常,异常信息如下:
```error
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'husbandBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:265)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)
... 44 more
通过测试得知,当循环依赖的所有 Bean 的 scope="prototype"的时候,产生的循环依赖,Spring 是无法解决的,会出现 BeanCurrentlyInCreationException 异常。
大家可以测试一下,以上两个 Bean,如果其中一个是 singleton,另一个是 prototype,是没有问题的。
为什么两个 Bean 都是 prototype 时会出错呢? ![[09.03-1.png]]
9.4 singleton 下的构造注入产生的循环依赖¶
我们再来测试一下 singleton + 构造注入的方式下,spring 是否能够解决这种循环依赖。
```java title:Husband package com.powernode.spring6.bean2;
/** * @author 动力节点 * @version 1.0 * @className Husband * @since 1.0 */ public class Husband { private String name; private Wife wife;
public Husband(String name, Wife wife) {
this.name = name;
this.wife = wife;
}
// -----------------------分割线--------------------------------
public String getName() {
return name;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife +
'}';
}
}
java title:Wife
package com.powernode.spring6.bean2;
/**
* @author 动力节点
* @version 1.0
* @className Wife
* @since 1.0
*/
public class Wife {
private String name;
private Husband husband;
public Wife(String name, Husband husband) {
this.name = name;
this.husband = husband;
}
// -------------------------分割线--------------------------------
public String getName() {
return name;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband +
'}';
}
}
```
```xml title:spring2.xml
<bean id="hBean" class="com.powernode.spring6.bean2.Husband" scope="singleton">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="wife" ref="wBean"/>
</bean>
<bean id="wBean" class="com.powernode.spring6.bean2.Wife" scope="singleton">
<constructor-arg name="name" value="小花"/>
<constructor-arg name="husband" ref="hBean"/>
</bean>
java title:CircularDependencyTest
@Test
public void testSingletonAndConstructor(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
Husband hBean = applicationContext.getBean("hBean", Husband.class);
Wife wBean = applicationContext.getBean("wBean", Wife.class);
System.out.println(hBean);
System.out.println(wBean);
}
```
执行结果:发生了异常,信息如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'hBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)
... 56 more
和上一个测试结果相同,都是提示产生了循环依赖,并且 Spring 是无法解决这种循环依赖的。 为什么呢?
主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。
9.5 Spring 解决循环依赖的机理¶
Spring 为什么可以解决 set + singleton 模式下循环依赖? 根本的原因在于:这种方式可以做到将“实例化 Bean”和“给 Bean 属性赋值”这两个动作分开去完成。
实例化 Bean 的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该 Bean 对象“曝光”给外界。
给 Bean 属性赋值的时候:调用 setter 方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean 都是单例的,我们可以先把所有的单例 Bean 实例化出来,放到一个**当中(我们可以称之为缓存),所有的单例 Bean 全部实例化完成之后,以后我们再慢慢的调用 setter 方法给属性赋值。这样就解决了循环依赖的问题。
那么在 Spring 框架底层源码级别上是如何实现的呢?请看: ![[09.05-1.png]]
在以上类中包含三个重要的属性:
* Cache of singleton objects: bean name to bean instance. 单例对象的缓存:key 存储 bean 名称,value 存储 Bean 对象【一级缓存】
* Cache of early singleton objects: bean name to bean instance. 早期单例对象的缓存:key 存储 bean 名称,value 存储早期的 Bean 对象【二级缓存】
* Cache of singleton factories: bean name to ObjectFactory. 单例工厂缓存:key 存储 bean 名称,value 存储该 Bean 对应的 ObjectFactory 对象【三级缓存】
这三个缓存其实本质上是三个 Map。
我们再来看,在该类中有这样一个方法 addSingletonFactory (),这个方法的作用是:将创建 Bean 对象的 ObjectFactory 对象提前曝光。
![[09.05-2.png]]
再分析下面的源码:
![[09.05-3.png]]
从源码中可以看到,spring 会先从一级缓存中获取 Bean,如果获取不到,则从二级缓存中获取 Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的 ObjectFactory 对象,通过 ObjectFactory 对象获取 Bean 实例,这样就解决了循环依赖的问题。
总结: Spring 只能解决 setter 方法注入的单例 bean 之间的循环依赖。 ClassA 依赖 ClassB,ClassB 又依赖 ClassA,形成依赖闭环。 Spring 在创建 ClassA 对象后,不需要等给属性赋值,直接将其曝光到 bean 缓存当中。
在解析 ClassA 的属性时,又发现依赖于 ClassB,再次去获取 ClassB,当解析 ClassB 的属性时,又发现需要 ClassA 的属性,但此时的 ClassA 已经被提前曝光加入了正在创建的 bean 的缓存中,则无需创建新的的 ClassA 的实例,直接从缓存中获取即可。从而解决循环依赖问题。