Bean的循环依赖问题
循环依赖: A对象中有B属性 , B对象中有A属性(丈夫类Husband中有Wife的引用, 妻子类Wife中有Husband的引用)
toString()
方法重写时直接输出wife/husband
会出现递归导致的栈内存溢出错误
- 直接输出wife/husband会调用它们的toString()方法, 在toString()方法又会调用husband/wife的toString()方法一直循环
//丈夫类
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方法进行属性的赋值- 注意: 只有在scope是singleton的情况下,Bean才会采取提前曝光的措施 , 多实例下不会曝光
<?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">
<!--singleton表示在整个Spring容器当中是单例的,独一无二的对象-->
<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异常
- 当两个bean的scope都是prototype的时候才会出现Bean正在处于创建中异常 , 如果其中任意一个是singleton的就不会出现异常
<?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">
<!--由于wifeBean/husbandBean是多实例的,只有每一次执行getBean()方法的时候才会创建新的Bean,故会造成死循环-->
<!--ref相当于从容器中手动获取对象,由于其是多实例的,所以每次获取的都是新对象-->
<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>
<!--因为wifeBean是单实例的会提前曝光,所以husbandBean可以new新对象-->
<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<!--当两个bean的scope都是prototype的时候才会出现异常,如果其中任意一个是singleton的就不会出现异常-->
<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 +
'}';
}
}
<?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>
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实例并放到二级缓存