当前位置: 首页 > 图灵资讯 > 技术篇> Bean的循环依赖问题

Bean的循环依赖问题

来源:图灵教育
时间:2023-06-07 09:48:32

 

1.Bean的循环依赖是什么?54

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。

例如:丈夫类Husband,妻子类Wife。Husband中有Wife引用。Wife中有Husband引用。

Bean的循环依赖问题_spring

 

package com.powernode.spring6.bean;/** *  Bean的循环依赖是什么?  54 * 丈夫类  54 **/public class Husband {    private String name;    private Wife wife;    public void setName(String name) {        this.name = name;    }    public String getName() {        return name;    }    public void setWife(Wife wife) {        this.wife = wife;    }    @Override    public String toString() {        return "Husband{" +                "name='" + name + '\'' +                ", wife=" + wife.getName() +                '}';    }}

 

 

package com.powernode.spring6.bean;/** *  Bean的循环依赖是什么?  54 * 妻子类 **/public class Wife {    private String name;    private Husband husband;    public void setName(String name) {        this.name = name;    }    public String getName() {        return name;    }    public void setHusband(Husband husband) {        this.husband = husband;    }    @Override    public String toString() {        return "Wife{" +                "name='" + name + '\'' +                ", husband=" + husband.getName() +                '}';    }}

 

2.singleton下set注入的循环依赖于54

让我们编写程序,测试Spring在singleton+setter模式下产生的循环依赖性。Spring能解决吗?

 

<!--singleton + setter模式下的循环依赖没有问题。-->    <!--singleton表示,在整个spring容器中,它是一个单例、独特的对象。-->    <!--        在singleton中 + 为什么Spring在setter模式下不会出现循环依赖问题?            主要原因是Spring对Bean的管理在这种模式下主要分为两个阶段:                第一阶段:Spring容器加载时,实例化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>

 

 

//singleton + setter模式下的循环依赖没有问题。    @Test    public void testCD(){        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);        System.out.println(husbandBean);        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);        System.out.println(wifeBean);    }

 

Bean的循环依赖问题_赋值_02

通过测试了解到,在singleton+set注入的情况下,循环依赖没有问题。spring可以解决这个问题。

2.1为什么Spring在singleton+setter模式下不会出现循环依赖问题?55

主要原因是Spring对Bean的管理在这种模式下主要分为两个阶段:

第一阶段:Spring容器加载时,实例Bean,只要其中任何一个Bean实例化,就会立即“曝光”【不等属性赋值曝光】

第二阶段:Bean“曝光”后,赋值属性(调用set方法)。

核心解决方案是:实例对象和对象的属性赋值分为两个阶段。

注:只有scope是sing在leton的情况下,Bean会提前采取“曝光”措施。

3.set注入在prototype下产生的循环依赖于56

注:当两个bean的scope都是prototype时,就会出现异常。如果其中任何一个是singleton,则不会出现异常。

让我们再试一次:prototype+循环依赖会在set注入的方式下出现问题吗?

 

<!--prototype + setter模式下的循环依赖存在问题和异常!-->    <!--BeanCurrentlyInCreationException 目前,Bean正处于创作异常状态。。。-->    <!-- 注:当两个bean的scope都是prototype时,就会出现异常。    如果其中任何一个是singleton,就不会出现异常。-->    <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>

 

 

///prototype + setter模式下的循环依赖存在问题和异常!  56    @Test    public void testCD1(){        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);        System.out.println(husbandBean);        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);        System.out.println(wifeBean);    }

 

Bean的循环依赖问题_Bean循环依赖_03

3.1执行测试程序:异常信息如下:56

Causedby:org.springframework.beans.factory.BeanCurrentlyInCreationException:Errorcreatingbeanwithname'husbandBean':Requestedbeaniscurrentlyincreation:Isthereanunresolvablecircularreference?

atorg.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:265)

atorg.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

atorg.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)

...44more

翻译名称为“创建”husbandBean“bean出错:请求的bean目前正在创建中:是否存在无法分析的循环引用?

通过测试了解到,当循环依赖所有Bean的scope=“prototype“当循环依赖产生时,Spring无法解决,Beancurentlyincreationexception会出现异常。

可以测试以上两个Bean,如果其中一个是singleton,另一个是prototype,那就没问题了。

为什么两个Bean都是prototype时会出错?56

Bean的循环依赖问题_Bean循环依赖_04

4.singleton下的结构注入产生的循环依赖于57

让我们来测试一下spring是否能解决singleton+结构注入的循环依赖。

 

package com.powernode.spring6.bean2;/** * 在singleton下注入结构产生的循环依赖  57 * 丈夫类 **/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.getName() +                '}';    }}

 

 

package com.powernode.spring6.bean2;/** * 在singleton下注入结构产生的循环依赖  57 * 妻子类 **/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.getName() +                '}';    }}

 

spring2.xml

 

<?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下注入结构产生的循环依赖  57-->    <!--    在singleton下注入结构产生的循环依赖  57-->    <!--这种循环依赖是否存在结构注入问题?-->    <!--注:基于结构注入的循环依赖也无法解决,因此在编写代码时必须注意。-->    <bean id="h" scope="singleton" class="com.powernode.spring6.bean2.Husband">        <constructor-arg index="0" value="张三"></constructor-arg>        <constructor-arg index="1" ref="w"></constructor-arg>    </bean>    <bean id="w" scope="singleton" class="com.powernode.spring6.bean2.Wife">        <constructor-arg index="0" value="小花"></constructor-arg>        <constructor-arg index="1" ref="h"></constructor-arg>    </bean></beans>

 

 

///singleton下的结构注入产生的循环依赖  57    @Test    public void TestCD2(){        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(spring2.xml");        Husband husbandBean = applicationContext.getBean("h", Husband.class);        System.out.println(husbandBean);        Wife wifeBean = applicationContext.getBean("w", Wife.class);        System.out.println(wifeBean);    }

 

Bean的循环依赖问题_spring_05

与上一次测试结果相同,都提示产生循环依赖,Spring无法解决这种循环依赖。

为什么呢?

主要原因是注入结构方法:由于注入结构方法会导致实例对象的过程和对象属性赋值的过程不分离,必须一起完成。

5.解决循环依赖的Spring机制58为什么5.1Spring能解决set+singleton模式下的循环依赖?58

根本原因是,这种方法可以分别完成“实例化Bean”和“赋予Bean属性”两个动作。

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

赋值Bean属性时:调用setter方法完成。

两个步骤可以分开完成,这两个步骤不需要在同一时间点完成。

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

那么Spring框架底层源码级别是如何实现的呢?请参见:

Bean的循环依赖问题_Bean循环依赖_06

上述类别包括三个重要属性:

源码分析:

在DefaultsingletonBeanregistry类中,有三种更重要的缓存: