Spring:Bean的循环依赖问题

引例

什么是Bean的循环依赖

A对象中有B属性,B对象中有A属性,这就是循环依赖,例如:

有两个类,丈夫Husband和妻子Wife,Husband中有Wife的引用,Wife中有Husband的引用

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() +
        '}';
    }
}
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() +
        '}';
    }
}

Spring配置文件spring.xml:

<bean id="husbandBean" class="Husband" scope="singleton">
  <property name="name" value="狗蛋"/>
  <property name="wife" ref="wifeBean"/>
</bean>

<bean id="wifeBean" class="Wife" scope="singleton">
  <property name="name" value="翠花"/>
  <property name="husband" ref="husbandBean"/>
</bean>

两个Bean的scope都是“singleton”,scope="singleton"表示这个Bean在Spring容器中是单例的,即整个应用中只有一个实例。使用的是set的方式给属性赋值,wife和husband循环依赖

测试类:

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

执行结果:

img

执行结果没有问题

singleton+setter模式下的循环依赖是没问题的,Spring是如何应对的呢?

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

  • 第一阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化后,马上进行“曝光”,不等属性赋值
  • 第二阶段:Bean“曝光之后”,再进行属性的赋值(调用set方法)

核心解决方案:实例化对象和对象的属性赋值分为两个阶段来完成

注:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施,singleton表示在整个Spring容器中是单例的,独一无二的对象

拿上述引例的spring.xml来说

img

当Spring容器启动时,会读取配置文件并创建Bean

  • 1行执行时<bean id="husbandBean" class="Husband" scope="singleton">,Spring容器尝试创建husbandBean,并“曝光”,此时husbandBean的属性都还没有赋值
  • 执行第2<property name="name" value="狗蛋"/>name属性赋值“狗蛋”
  • 执行第3<property name="wife" ref="wifeBean"/>给wife属性赋值,此时会查找名为wifeBean的Bean,由于wifeBean还未创建,Spring容器会先创建wifeBean
  • 执行第6<bean id="wifeBean" class="Wife" scope="singleton">,Spring容器尝试创建wifeBean,先创建出wifeBean这个Bean,并“曝光”,此时wifeBean的属性都还没有赋值
  • wifeBean“曝光后”,将其注入到husbandBean的wife属性,此时注入的wife属性只是一个半成品,但是后面都会给其属性赋值
  • 执行第7<property name="name" value="翠花"/>,给name属性赋值“翠花”
  • 执行第8<property name="husband" ref="husbandBean"/>将husbandBean注入到husband属性

注意:wifeBean“曝光”后,可以放心大胆的注入到husbandBean的wife属性,这是因为wifeBean是单例的,整个Spring容器中只有一个

多例下的循环依赖

我们将两个Bean修改成多例的scope="prototype

<bean id="husbandBean" class="Husband" scope="prototype">
  <property name="name" value="狗蛋"/>
  <property name="wife" ref="wifeBean"/>
</bean>

<bean id="wifeBean" class="Wife" scope="prototype">
  <property name="name" value="翠花"/>
  <property name="husband" ref="husbandBean"/>
</bean>

执行测试程序

@Test
public void test1(){
    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);
}

就会出现BeanCurrentlyInCreationException异常(当前的Bean正处于创建中异常)

img

原因分析

img

还是分析spring.xml,

  • 执行第1<bean id="husbandBean" class="Husband" scope="prototype">,Spring容器尝试创建husbandBean
  • 执行第2<property name="name" value="狗蛋"/>name属性赋值“狗蛋”
  • 执行第3<property name="wife" ref="wifeBean"/>,给wife属性赋值,此时会查找名为wifeBean的Bean,由于wifeBean还未创建,Spring容器会先创建wifeBean
  • 执行第6<bean id="wifeBean" class="Wife" scope="singleton">,Spring容器尝试创建wifeBean,先创建出wifeBean这个Bean,由于scope="prototype",所以不会“曝光”,wifeBean创建出来并不会注入到husbandBean的wife属性
  • 执行第7<property name="name" value="翠花"/>,给name属性赋值“翠花”
  • 执行第8<property name="husband" ref="husbandBean"/>,由于husbandBean的scope="prototype",所以Spring容器会尝试new一个新的husbandBean,并不会把之前的husbandBean注入到wifeBean的husband属性
  • 执行第1<bean id="husbandBean" class="Husband" scope="prototype">,Spring容器尝试创建husbandBean
  • 执行第2<property name="name" value="狗蛋"/>name属性赋值“狗蛋”
  • 执行第3<property name="wife" ref="wifeBean"/>,给wife属性赋值,此时会查找名为wifeBean的Bean,由于wifeBean的scope="prototype",,所以Spring容器会尝试new一个新的wifeBean,并不会把之前的wifeBean注入到husbandBean的wife属性
  • 执行第6<bean id="wifeBean" class="Wife" scope="singleton">,Spring容器尝试创建wifeBean

一直这样递归下去,所以导致报BeanCurrentlyInCreationException这个异常

singleton+prototype

以上两个Bean,如果一个是singleton,另一个是prototype,是没有问题的

<bean id="husbandBean" class="Husband" scope="singleton">
  <property name="name" value="狗蛋"/>
  <property name="wife" ref="wifeBean"/>
</bean>

<bean id="wifeBean" class="Wife" scope="prototype">
  <property name="name" value="翠花"/>
  <property name="husband" ref="husbandBean"/>
</bean>
  • 执行第1<bean id="husbandBean" class="Husband" scope="singleton">,Spring容器尝试创建husbandBean,并“曝光”
  • 执行第2<property name="name" value="狗蛋"/>name属性赋值“狗蛋”
  • 执行第3<property name="wife" ref="wifeBean"/>,给wife属性赋值,此时会查找名为wifeBean的Bean,由于wifeBean还未创建,Spring容器会先创建wifeBean
  • 执行第6<bean id="wifeBean" class="Wife" scope="singleton">,Spring容器尝试创建wifeBean,先创建出wifeBean这个Bean,由于scope="prototype",所以不会“曝光”,wifeBean创建出来并不会注入到husbandBean的wife属性
  • 执行第7<property name="name" value="翠花"/>,给name属性赋值“翠花”
  • 执行第8<property name="husband" ref="husbandBean"/>,由于husbandBean已经被“曝光”了,直接将其注入到wifeBean的husband属性

singleton下的构造注入产生的循环依赖

我们使用singleton下的构造注入,spring2.xml:

<bean id="husbandBean" scope="singleton" class="Husband">
        <constructor-arg index="0" value="狗蛋"></constructor-arg>
        <constructor-arg index="1" ref="wifeBean"></constructor-arg>
    </bean>

    <bean id="wifeBean" scope="singleton" class="Wife">
        <constructor-arg index="0" value="翠花"></constructor-arg>
        <constructor-arg index="1" ref="husbandBean"></constructor-arg>
    </bean>

类中提供构造方法

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() +
        '}';
    }
}
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() +
        '}';
    }
}

测试类

@Test
public void test2(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");

    Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
    System.out.println(husbandBean);

    Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
    System.out.println(wifeBean);
}

结果依然报错

img

这是因为构造方法执行是对象创建的标志,构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开。所以构造注入不能解决循环依赖问题!

Spring解决循环依赖的机理

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

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

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

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

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

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

那么在Spring框架底层源码级别上是如何实现的呢?

在org.springframework.beans.factory.support包下的AbstractAutowireCapableBeanFactory类中有doCreateBean,这是创建Bean对象的整体过程,其中:

img

img

其中将对象放缓存,即“曝光”这一步,看org.springframework.beans.factory.support包下的DefaultSingletonBeanRegistry

img

DefaultSingletonBeanRegistry类中有三个比较重要的缓存:

  • private final Map<String, Object> singletonObjects 一级缓存

  • private final Map<String, Object> earlySingletonObjects 二级缓存

  • private final Map<String, ObjectFactory<?>> singletonFactories 三级缓存

    这三个缓存都是Map集合。
    Map集合的key存储的都是bean的name(bean id)。

    一级缓存存储的是完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了。是一个完整的Bean对象。
    二级缓存存储的是早期的单例Bean对象。这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。
    三级缓存存储的是单例工厂对象。这个里面存储了大量的“工厂对象”,每一个单例Bean对象都会对应一个单例工厂对象。这个缓存中存储的是,创建该单例对象时对应的那个单例工厂对象。

在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光。

img

再看getSingleton()方法,该方法作用是获取Bean

img

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java老狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值