八 Bean循环依赖

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);
}

结果如下

观察这个结果首先得出结论 的的确确解决了循环依赖问题 然后我们仔细看一下这个执行流程

  1. 先创建对象

  2. 然后才开始给属性赋值

从这个结果我们可以大概知道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是如何解决循环依赖的 拭目以待!

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值