循环依赖: A对象中有B属性 , B对象中有A属性(丈夫类Husband中有Wife的引用, 妻子类Wife中有Husband的引用)
toString()
方法重写时直接输出wife/husband
会出现递归导致的栈内存溢出错误
//丈夫类
public class Husband {
private String name;
private Wife wife;
// 属性的setter方法
// toString()方法重写时直接输出wife会出现递归导致的栈内存溢出错误,需要输出wife.getName()
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
//妻子类
public class Wife {
private String name;
private Husband husband;
//属性的setter方法
// toString()方法重写时不能直接输出husband,需要输出husband.getName()
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
singleton下的set注入
Spring可以解决在singleton+setter
模式下出现的循环依赖问题, 因为在这种模式下Spring对Bean的管理主要分为清晰的两个阶段
实例化对象阶段
:Spring容器实例化Bean的时候会先创建对象 , 不等其属性赋值就会曝光加入正在创建的bean的缓存中 , 这样其他bean就可以直接引用它对象的属性赋值阶段
:Bean曝光
之后,再调用set方法进行属性的赋值
<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.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>
beans>
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);
//Husband{name="张三",wife=小花}
System.out.println(husbandBean);
//Wife{name="小花",husband=张三}
System.out.println(wifeBean);
}
}
prototype下的set注入
若循环依赖的所有Bean的scope="prototype"
时 , Spring无法解决它们产生的循环依赖问题,此时会出现BeanCurrentlyInCreationException异常
<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.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>
<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="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
bean>
beans>
singleton下的构造注入
Spring无法解决singleton下的构造注入产生的循环依赖
,会出现BeanCurrentlyInCreationException异常
//丈夫类
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 +
'}';
}
}
//妻子类
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 +
'}';
}
}
<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>
Spring解决循环依赖的原理
Spring解决set + singleton模式下循环依赖的问题是将实例化Bean
和给Bean属性赋值
这两个动作分开去完成(这两步不要求在同一个时间点上完成)
先实例化单实例的Bean
: 通过调用无参数构造方法时把所有的单例Bean实例化出来,放到一个Map集合(缓存) 当中曝光给外界然后给Bean属性赋值
:调用setter方法来完成对象的属性赋值Spring框架底层源码级别的缓存实现
完整单例对象的缓存
:key存储bean名称,value存储的单例Bean对象属性都已经赋值【一级缓存】早期单例对象的缓存
:key存储bean名称,value存储早期的Bean对象属性没有赋值【二级缓存】单例工厂对象的缓存
:key存储bean名称,value存储每个Bean对象对应的ObjectFactory单例工厂对象【三级缓存】在AbstractAutowireCapableBeanFactory类的doCreateBean()
方法完成Bean的创建和属性赋值
creatBeanInstance
只会创建Bean对象, addSingletonFactory
方法中将bean对象缓存起来然后曝光, populateBean
方法填充bean即给bean的属性赋值
DefaultSingletonBeanRegistry的addSingletonFactory()方法的作用是将创建Bean对象的ObjectFactory工厂对象提前曝光
spring会先从一级缓存
中获取Bean,获取不到则从二级缓存
中获取Bean,如果还是获取不到则从三级缓存
中获取之前曝光的ObjectFactory对象,然后通过ObjectFactory
对象获取Bean实例并放到二级缓存