8.1 何为循环依赖??
假设A类中有一个属性是B类 而B类中有一个属性是A类 这就是循环依赖
举个例子来更好的说明这个问题
其中 Husband有Wife这个类的依赖 Wife这个类有Husband这个类的依赖
形成一种你依赖我 我依赖你的这种相互依赖就是循环依赖
那么接下来我们就来探讨下spring对于循环依赖的解决方法
8.2 singleton+set注入能否解决循环依赖
不知道对singleton还是否有印象呢 我们知道在spring的IoC容器中 所有的Bean对象默认都是单例模式(singleton)
所以我们来用代码验证下在单例模式和set注入下能否解决循环依赖呢?
老规矩 给Bean对象 我们就按照最开始的例子 丈夫“依赖”妻子 妻子“依赖”丈夫
// 丈夫 public class Husband { private String name; private Wife wife; @Override public String toString() { return "Husband{" + "name='" + name + '\'' + ", wife=" + wife.getName() + '}'; } public Husband(){ System.out.println("Hunband初始化"); } public void setName(String name) { System.out.println("name属性赋值"); this.name = name; } public String getName() { return name; } public void setWife(Wife wife) { System.out.println("wife属性赋值"); this.wife = wife; } } //妻子 public class Wife { private String name; private Husband husband; @Override public String toString() { return "Wife{" + "name='" + name + '\'' + ", husband=" + husband.getName() + '}'; } public void setName(String name) { System.out.println("Wife name属性赋值"); this.name = name; } public void setHusband(Husband husband) { System.out.println("Husband 属性赋值"); this.husband = husband; } public Wife(){ System.out.println("wife初始化"); } public String getName() { return name; } }
toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。
xml文件
<bean id="hunsband" class="com.myStudy.Husband"> <property name="name" value="小明"></property> <property name="wife" ref="wife"></property> </bean> <bean id="wife" class="com.myStudy.Wife"> <property name="name" value="小红"></property> <property name="husband" ref="hunsband"></property> </bean>
编写测试代码准备开测! (#`д´)ノ
public void SetterSingleton(){ ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring.xml"); Husband hunsband = applicationContext.getBean("hunsband", Husband.class); Wife wife = applicationContext.getBean("wife", Wife.class); System.out.println(hunsband); System.out.println(wife); }
结果如下
观察这个结果首先得出结论 的的确确解决了循环依赖问题 然后我们仔细看一下这个执行流程
-
先创建对象
-
然后才开始给属性赋值
从这个结果我们可以大概知道setter+singleton如何解决了循环依赖 实例化对象后先不急着给属性赋值 先将对象"暴露"出来 这个时候如果有其他对象引用此对象 可以放心的引用 因为是单例模式 可以大胆"暴露" 等所有对象都实例化之后 才开始慢慢的属性赋值 这个过程其实也就是三级缓存 下一章我们仔细探讨一下哈
说简单点就是 spring对于bean管理有两个非常清晰的阶段 1. 实例化 当一个bean创建好后 spring会马上让这个bean进行“曝光” 意味着其他可以直接引入这个依赖 2. 属性赋值 bean曝光后在进行属性赋值
8.3 setter+prototype 能否解决循环依赖
先说结论 🙅
其实上代码前我们可以简单的分析一下
为什么 setter+singleton可以解决呢?因为spring会先暴露对象 再赋值
那为什么spring敢先暴露对象呢? 因为在spring中 对象默认都是单例的 所以别的类需要引用的话直接就能找到对应的对象
如果是多例模式呢? 我们可以画个小图
在这里你告诉我 这个丈夫的妻子具体指的是哪一个wife对象呢 spring搞不明白的 所以setter+prototype解决不了循环依赖
不过为了严谨我们还是上代码验证一下
只需要改一下xml就可以
<bean id="hunsband" class="com.myStudy.Husband" scope="prototype"> <property name="name" value="小明"></property> <property name="wife" ref="wife"></property> </bean> <bean id="wife" class="com.myStudy.Wife" scope="prototype"> <property name="name" value="小红"></property> <property name="husband" ref="hunsband"></property> </bean>
ok 测试代码一样就不看了 直接结果
理所应当报错了 这里借助下万能的gpt较为严谨的解释下
定义了一个丈夫和一个妻子的bean,并且将它们的引用作为属性注入到对方的bean中。 由于这两个bean的scope属性都是prototype,即每次获取bean都会创建一个新实例, 而且在创建完对象之后会先进行属性赋值,这就导致了循环依赖的问题。
8.4 setter+构造方法模式
直接说结论 🙅
直接上代码测一下吧
xml
<bean id="h" class="com.myStudy.Coustor.Husband"> <constructor-arg index="0" value="老王" > </constructor-arg> <constructor-arg index="1" ref="w"></constructor-arg> </bean> <bean id="w" class="com.myStudy.Coustor.Wife"> <constructor-arg index="0" value="小花"></constructor-arg> <constructor-arg index="1" ref="h"></constructor-arg> </bean>
Bean
public class Husband { private String name; private Wife wife; @Override public String toString() { return "Husband{" + "name='" + name + '\'' + ", wife=" + wife.getName() + '}'; } // 有参构造方法 public Husband(String name, Wife wife) { System.out.println("有参构造"); this.name = name; this.wife = wife; } public String getName() { return name; } public void setWife(Wife wife) { System.out.println("wife属性赋值"); this.wife = wife; } } //妻子 public class Wife { private String name; private Husband husband; @Override public String toString() { return "Wife{" + "name='" + name + '\'' + ", husband=" + husband.getName() + '}'; } public Wife(String name, Husband husband) { System.out.println("Wife有参构造"); this.name = name; this.husband = husband; } public String getName() { return name; } }
不废话了 直接上测试代码看效果
public void CotorSingleton(){ ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring2.xml"); com.myStudy.Coustor.Husband h = applicationContext.getBean("h", com.myStudy.Coustor.Husband.class); com.myStudy.Coustor.Wife w = applicationContext.getBean("w", com.myStudy.Coustor.Wife.class); System.out.println(h); System.out.println(w); }
直接红一片 反正就是一堆报错
这里不知道大家有没有注意到 在setter+prototype模式下
至少我们是创建出了对象 只是在属性赋值时A调用set方法创建了新的B 新的B又调用set方法通过无参构造又创建了新的A这样无限递归导致的栈溢出报错
而setter+构造方法模式下 连对象都无法创建因为
有参构造完成初始化的过程就自动完成了属性赋值 但因为丈夫依赖妻子 所以要创建丈夫的对象 通过有参构造就必须要创建妻子的对象 并给丈夫的属性赋值 但妻子的对象也创建不了 因为妻子同样依赖丈夫 两个对象都创建不了 和setter+prototype模式还不太一样 set加多例是循环创建 而此模式一个也创建不出来
简单来说就是把赋值和初始化捆绑在一起 丈夫需要妻子实例化完成才可以创建完毕 而妻子同样需要丈夫实例化完成才可以完成创建 这样两者相互矛盾的循环依赖就会导致连对象都new不出来
ok了家人们 下一章详细剖析一下setter+singleton是如何解决循环依赖的 拭目以待!