一、什么是 Bean 的循环依赖
A 对象中有 B 属性,B 对象中有 A 属性,这就是循环依赖,我依赖你,你也依赖我
比如:丈夫类 Husband,妻子类 Wife。Husband 中有Wife 的引用,Wife 中有 Husband 的引用
public class Husband {
private String name;
private Wife wife;
}
public class Wife {
private String name;
private Husband husband;
}
二、singleton 下的 set 注入产生的循环依赖
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() +
'}';
}
}
public class Husband {
private String name;
private Wife wife;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
// toString()方法重写时需要注意:不能直接输出wife,输出wife.getName(),要不然会出现递归导致的栈内存溢出错误
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
<bean id="husbandBean" class="org.qiu.spring.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="org.qiu.spring.bean.Wife" scope="singleton">
<property name="name" value="小芳"/>
<property name="husband" ref="husbandBean"/>
</bean>
@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);
}
运行结果:
通过测试得知:在 singleton + set 注入的情况下,循环依赖是没有问题的,Spring可以解决这个问题
在 singleton + setter 模式下,为什么循环依赖不会出现问题,Spring 是如何应对的?
主要的原因是在这种模式下 Spring 对 Bean 的管理主要分为清晰的两个阶段:
第一个阶段:在 Spring 容器加载的时候,实例化 Bean,只要其中任意一个 Bean 实例化之后,马上进行“曝光”【不等属性赋值】
第二个阶段:Bean “曝光” 之后,再进行属性的赋值(调用 setter 方法)
核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。
三、prototype 下的 set 注入产生循环依赖
<bean id="husbandBean" class="org.qiu.spring.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="org.qiu.spring.bean.Wife" scope="prototype">
<property name="name" value="小芳"/>
<property name="husband" ref="husbandBean"/>
</bean>
执行结果:
翻译为:创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?
通过测试得知,当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常。
大家可以测试一下,以上两个Bean,如果其中一个是singleton,另一个是prototype,是没有问题的
为什么两个Bean都是prototype时会出错呢?
四、singleton 下的构造注入产生的循环依赖
package org.qiu.spring.bean;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.spring.bean
* @date 2022-11-11-16:10
* @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 +
'}';
}
}
package org.qiu.spring.bean;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.spring.bean
* @date 2022-11-11-16:10
* @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 +
'}';
}
}
<bean id="husbandBean" class="org.qiu.spring.bean.Husband" scope="singleton">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="org.qiu.spring.bean.Wife" scope="singleton">
<constructor-arg name="name" value="小芳"/>
<constructor-arg name="husband" ref="husbandBean"/>
</bean>
执行结果:
和上一个测试结果相同,都是提示产生了循环依赖,并且 Spring 是无法解决这种循环依赖的
为什么呢?
主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的
五、Spring 解决循环依赖的机理
Spring为什么可以解决set + singleton模式下循环依赖?
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。
给Bean属性赋值的时候:调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
那么在Spring框架底层源码级别上是如何实现的呢?
在以上类中包含三个重要的属性:
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对象提前曝光
从源码中可以看到,Spring 会先从一级缓存中获取 Bean,如果获取不到,则从二级缓存中获取 Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的 ObjectFactory 对象,通过 ObjectFactory 对象获取 Bean 实例,这样就解决了循环依赖的问题
一叶知秋,奥妙玄心