Bean的循环依赖及Spring解决循环依赖的机理

创建两个类:

①丈夫类:

public class Husband {
    private String name;
    private Wife wife;

    public void setName(String name) {
        this.name = name;
    }

    //此处创建get方法供Wife类中的toStirng方法调用:husband.getName()
    public String getName() {
        return name;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName()+  
                '}';
    }
}

②妻子类:

public class Wife {
    private String name;
    private Husband husband;

    public void setName(String name) {
        this.name = name;
    }

    //此处创建get方法供Husband类中的toStirng方法调用:wife.getName()
    public String getName() {
        return name;
    }

    public void setHusband(Husband husband) {
        this.husband = husband;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName()+
                '}';
    }
}

配置容器:

<!--setter 注入下的循环依赖-->

<!--singleton + setter 模式下的循环依赖-->
    <!--singleton 表示在整个Spring容器当中的单例的,独一无二的对象。-->
    <!--
        在singleton + setter 模式下,为什么循环依赖不会出现问题?Spring是如何应对的?
            主要的原因是,在这种模式下Spinrg对Bean的管理主要分为清晰的两个阶段:
                第一个阶段:单例模式下在Spring容器加载的时候(即applicationContext创建的时候),实例化Bean,只要其中任意一个Bean实例化之后,马上进行“曝光”【不等属性赋值就曝光】
                第二个阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法)。

             核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。
       注意:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施。

    -->
    <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>



    <!--prototype + setter 模式下的循环依赖-->
    <!--BeanCurrentlyInCreationException 当前的Bean正在处于创建中异常。。。-->
    <!--注意:当两个bean的scope都是prototype的时候才会出现异常,如果其中任意一个是singleton就不会出现异常。-->
    <bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
        <property name="name" value="张三"/>
        <!--因为wifeBean是多例,此处每次引用wifeBean,下边都会创建出一个新的wifeBean-->
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
        <property name="name" value="小花"/>
        <!--因为husbandBean是多例,此处每次引用husbandBean,下边都会创建出一个新的wifeBean,这样两者重复交替无限循环-->
        <property name="husband" ref="husbandBean"/>
    </bean>

注意:setter注入下循环依赖可以成功,但是构造注入下循环依赖不会成功。
因为 构造注入 只有在 构造方法 执行完后才能才能创建出对象。
即:在这个创建过程中就已经完成给属性赋值。
也就是说只有在给属性赋值完成后才能成功创建bean

那么,在husbandBean创建过程中,引用wifeBean的时候,wifeBean并没有被赋值也没有被创建出来,所以引用是不能成功的
同理在wifeBean创建过程中,引用HusbandBean的时候,HusbandBean并没有被赋值也没有被创建出来,所以引用也是不能成功的

结论:构造注入下循环依赖不会成功

Spring解决循环依赖的机理

Spring为什么可以解决set + singleton模式下循环依赖?

根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。

实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。

给Bean属性赋值的时候:调用setter方法来完成。

两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。

也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。

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对象【三级缓存】

这三个缓存其实本质上是三个Map集合,Map集合中的key存储的都是bean的name(bean id)

一级缓存存储的是:单例Bean对象。完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了。是一个完整的Bean对象。

二级缓存存储的是:早期的单例Bean对象。这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。

三级缓存存储的是:单例工厂对象。这个里面存储了大量的“工厂对象",每一个单例Bean对象都会对应一个单例工厂对象。这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象。

从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。

总结:

Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值