9.1 什么是Bean的依赖循环
A对象中有B属性 . B属性中有A属性 . A和B如果需要完全被定义就需要依赖对方 这就是依赖循环,
9.2singleton下的set注入产生的依赖循环
首先给两个有循环依赖的类
package com.spring6.bean;
/**
* @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() +
'}';
}
}
package com.spring6.bean;
/**
* @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 version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="husbandBean" class="com.spring6.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.spring6.bean.Wife" scope="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>
经过测试后,在==singleton + set 注入的情况下,以循环依赖不会产生任何问题==
9.3prototype下set注入产生的循环依赖
只需要更改配置文件就可以变成prototype + set 注入的类型
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="husbandBean" class="com.spring6.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.spring6.bean.Wife" scope="prototype">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>
执行之后会报错
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
- 创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?
通过测试可得: 当使用scope="prototype" 时产生的循环依赖==无法解决==
9.3.1 无法解决的原因
9.4 singleton 下构造注入产生的循环依赖
Code:
package com.spring6.bean2;
/**
* @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 +
'}';
}
}
package com.spring6.bean2;
/**
* @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文件为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<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>
</beans>
测试之后报错
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?
........
主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。
9.5Spring解决循环依赖的机理
Spring为什么可以解决set + singleton模式下循环依赖?
- 因为这种方式将"实例化Bean" 和 "Bean属性赋值" 这两个步骤分开完成
- ==实例化Bean==时 ,调用无参构造方法. 不用给属性赋值, 可以提前将Bean对象曝光给外界
- 给Bean赋值时 , 调用setter方法去完成
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题
在以上类中包含三个重要的属性:
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对象【三级缓存】
- 当Spring尝试创建某个Bean(假设为A)时,首先会检查singletonObjects(一级缓存)中是否已经存在该Bean的实例。
- 如果没有,则开始实例化Bean A,并且在实例化之后立即将其以ObjectFactory的形式放入singletonFactories(三级缓存)中,同时尝试从缓存中获取它依赖的其他Bean(比如B)。
- 如果发现Bean B依赖于尚未完全初始化的Bean A(此时A还在创建过程中),则从singletonFactories(三级缓存)中获取Bean A的ObjectFactory,并通过它得到Bean A的一个早期引用,然后将这个早期引用放入earlySingletonObjects(二级缓存)中,供Bean B使用。
- 这样一来,当回到Bean A的创建过程时,就可以完成它的剩余初始化工作(如属性填充、初始化方法调用等)。最后,Bean A会被放入singletonObjects(一级缓存),并从singletonFactories(三级缓存)和earlySingletonObjects(二级缓存)中移除。