spring之Bean的循环依赖问题

时间:2022-12-25 19:53:48


一、Bean的循环依赖之Set注入模式下

A对象中有B属性,B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
比如:丈夫类Husband、妻子类Wife。Husband类中有Wife类的引用。Wife类中有Husband类的引用。

1、Husband类

husband类中有wife

注意:里边的toString方法对于wife这个属性使用了getName()避免陷入死循环

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

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

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

    public String getName() {
        return name;
    }

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

2、Wife类

wife类中有husband

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

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

    public String getName() {
        return name;
    }

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

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

3、Spring配置文件

配置两个bean

<?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">
    <bean id="husband" class="com.bjpowernode.spring.bean.Husband" scope="singleton">
        <property name="name" value="小白"></property>
        <property name="wife" ref="wife"></property>
    </bean>

    <bean id="wife" class="com.bjpowernode.spring.bean.Wife" scope="singleton">
        <property name="name" value="小川"></property>
        <property name="husband" ref="husband"></property>
    </bean>
</beans>

4、测试类

    @Test
    public void testDeprndency(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
        Husband husband = applicationContext.getBean("husband", Husband.class);
        System.out.println(husband);

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

5、测试结果

spring之Bean的循环依赖问题

6、结论

singleotn + setter模式下的循环依赖 spring是没有任何问题的。
singleotn 表示在整个Spring容器当中是单例的,独一无二的。

singleotn + setter模式下的循环依赖 spring是如何应对的?

主要原因是在这种模式下,Spring对Bean的管理主要分为清晰的两个阶段:
第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行“曝光”【不等属性赋值就曝光】
第二个阶段:Bean“曝光”之后,再进行属性的赋值。
核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。
只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施

prototy+ setter模式下的循环依赖 spring是会出现异常的

    <bean id="husband" class="com.bjpowernode.spring.bean.Husband" scope="prototype">
        <property name="name" value="小白"></property>
        <property name="wife" ref="wife"></property>
    </bean>

    <bean id="wife" class="com.bjpowernode.spring.bean.Wife" scope="prototype">
        <property name="name" value="小川"></property>
        <property name="husband" ref="husband"></property>
    </bean>

spring之Bean的循环依赖问题
Bean的循环依赖出现问题:BeanCurrentlyInCreationException
注意:当两个Bean的scope都是prototype的时候,才会出现异常,如果其中任意一个是singleton,就不会出现异常

二、Bean的循环依赖之构造方法注入模式下

1、Husband类

去掉set方法,加入构造方法

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

2、Wife类

去掉set方法,加入构造方法

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

    public Wife(String name, Husband husband) {
        this.name = name;
        this.husband = husband;
    }

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

    public String getName() {
        return name;
    }
}

3、Spring配置文件

构造注入,这种循环依赖是否会出现问题?

<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">
    <bean id="husband" class="com.bjpowernode.spring.bean.Husband" scope="singleton">
        <constructor-arg index="0" value="小白"></constructor-arg>
        <constructor-arg index="1" ref="wife"></constructor-arg>
    </bean>

    <bean id="wife" class="com.bjpowernode.spring.bean.Wife" scope="singleton">
        <constructor-arg index="0" value="小川"></constructor-arg>
        <constructor-arg index="1" ref="husband"></constructor-arg>
    </bean>
</beans>

4、测试类

    @Test
    public void testDeprndency(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
        Husband husband = applicationContext.getBean("husband", Husband.class);
        System.out.println(husband);

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

5、运行结果

spring之Bean的循环依赖问题
注意:基于构造注入的方式下产生的循环依赖也是无法解决的

Spring只能解决set+singleton模式下的循环依赖。

三、Spring解决循环依赖的机理

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

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

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

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

追源码:
双击shift: AbstractAutowireCapableBeanFactory
ctrl + f :doCreateBean 方法

debug:
spring之Bean的循环依赖问题
继续往下走一步:
这个husband对象的name属性和wife属性是空的,但是这个对象已经创建出来了
spring之Bean的循环依赖问题
走到下一个断点:
spring之Bean的循环依赖问题
把这个单例对象缓存起来,具体看看怎么缓存的:

三级缓存(面试常问)

step into进去一个新的类:DefaultSingletonBeanRegistry

spring之Bean的循环依赖问题

先说说这个类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对象都会对应一个单例工厂对象。这个集合中存储的是:创建该单例对象时对应的那个单例工厂对象

继续回到debug:
进来DefaultSingletonBeanRegistry这个类之后addSingletonFactory这个方法执行
spring之Bean的循环依赖问题
继续执行到:
并没有把Bean对象存进去,是把创建Bean对象的工厂对象存放到map集合。(三级缓存)
往map对象存的这个动作就叫做“曝光”
spring之Bean的循环依赖问题
“曝光”工厂之后会继续调用getSingleton方法
spring之Bean的循环依赖问题
然后从一级缓存取对象,拿不到从二级缓存取,拿不到从三级缓存取,
三级缓存取工厂对象,获取这个Bean对象,再把Bean对象放到二级缓存。
spring之Bean的循环依赖问题
最后执行
populateBean(beanName, mbd, instanceWrapper);才会给属性赋值