【Spring6】| Bean的循环依赖问题(含源码分析)

目录

一:Bean的循环依赖问题

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

2. singleton下的set注入产生的循环依赖(Spring可解决)

3. prototype下的set注入产生的循环依赖(Spring无法解决)

4. singleton下的构造注入产生的循环依赖(Spring无法解决)

5. Bean的循环依赖之源码分析


一:Bean的循环依赖问题

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

(1)所谓的循环依赖就是A对象中有B属性,B对象中有A属性;这就是循环依赖,我依赖你,你也依赖我!

(2)比如:丈夫类Husband,妻子类Wife;Husband中有Wife的引用,而Wife中有Husband的引用。

2. singleton下的set注入产生的循环依赖(Spring可解决)

我们来编写程序,测试一下在singleton(单例模式)+setter(set注入)的模式下产生的循环依赖,Spring是否能够解决?

Husband类:类中含有Wife的引用

首先我们先思考一下,在重写toString方法时需要注意:不能直接输出Wife的引用;如果直接输出引用,会造成一个死循环(一直相互调用对方的toString方法);所以我们不妨输出引用对应的名字(getName),所以我们需要再增加上一个获取名字的get方法!

package com.bjpowernode.spring.bean;

public class Husband {
    private String name;
    // 含有Wife的引用
    private Wife wife;

    public void setName(String name) {
        this.name = name;
    }
    // 增加上name的get方法
    public String getName() {
        return name;
    }

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

    // toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。
    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName()+
                '}';
    }
}

Wife类:类中含有Husband的引用

package com.bjpowernode.spring.bean;

public class Wife {
    private String name;
    // 含有Husband的引用
    private Husband husband;

    public void setName(String name) {
        this.name = name;
    }
    // 增加上name的get方法
    public String getName() {
        return name;
    }

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

    // toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。
    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

spring.xml配置

(1)singleton(单例)表示在整个Spring容器当中是单例的、独一无二的对象!

(2)分析:当给wife赋值时,Wife有可能还没有创建出来,所以Spring会把Wife创建出来,因为是单例的,所以只需要创建一次;并赋上值!当给husband赋值时,发现Husband实际已经创建出来了,不需要重新创建,所以直接赋上值即可!

<?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+setter下的循环依赖-->
    <bean id="husbandBean" class="com.bjpowernode.spring.bean.Husband">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.bjpowernode.spring.bean.Wife">
        <property name="name" value="小红"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
</beans>

编写测试程序

package com.bjpowernode.spring.test;

import com.bjpowernode.spring.bean.Husband;
import com.bjpowernode.spring.bean.Wife;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class CircularDependencyTest {
    @Test
    public void testCircularDependency(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        // 获取Husband对象
        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        System.out.println(husbandBean);
        // 获取Wife对象
        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(wifeBean);
    }
}

执行结果:可以正常输出

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

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

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

通过测试得知:在singleton + set注入的情况下,循环依赖是没有任何问题的,Spring可以解决这个问题!

3. prototype下的set注入产生的循环依赖(Spring无法解决)

我们再来测试一下:prototype+set注入的方式下,循环依赖会不会出现问题呢?

spring.xml配置当中,scop属性修改为property

(1)分析:singleton模式下创建ClassPathXmlApplicationContext对象时就会实例化对象,调用getBean方法只是从Map集合当中通过key获取value。property模式下是在调用getBean方法时才会创建对象。

(2)在创建Husband对象时,不会曝光!会给属性赋值,当给wife属性赋值时,会创建Wife对象;同样当创建Wife对象时会给husband属性赋值,因为是多例的,所以会创建一个新的Husband对象;这就会造成一个死循环!

<?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">
    <!--property+setter下的循环依赖-->
    <bean id="husbandBean" class="com.bjpowernode.spring.bean.Husband" scope="prototype">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.bjpowernode.spring.bean.Wife" scope="prototype">
        <property name="name" value="小红"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
</beans>

相同的代码进行测试

(1)翻译为:创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?

(2)通过测试得知,当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常(当前的Bean正在处于创建中异常)。

通过测试得知:在property + set注入的情况下,循环依赖Spring是无法解决的,会抛出BeanCurrentlyInCreationException异常

思考:如果其中一个是singleton,另一个是prototype呢?

首先创建ClassPathXmlApplicationContext对象时,singleton的对象Husband会被创建出来,并给另一个属性wife赋值,不存在---就会创建出来并赋值;当执行到getBean的时候,property的对象Wife会被创建出来(新创建的),并给另一个对象husband赋值,此时另一个对象是sinleton前面已经创建过了---直接赋值即可;完美结束!

<?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+property+setter下的循环依赖-->
    <bean id="husbandBean" class="com.bjpowernode.spring.bean.Husband" scope="singleton">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.bjpowernode.spring.bean.Wife" scope="prototype">
        <property name="name" value="小红"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
</beans>

通过测试得知:在property + singleton+set注入的情况下,循环依赖Spring是可以解决的

4. singleton下的构造注入产生的循环依赖(Spring无法解决)

我们再来测试一下singleton + 构造注入的方式下,spring是否能够解决这种循环依赖。

Husband类:必须含有构造方法

package com.bjpowernode.spring.bean;

public class Husband {
    private String name;
    // 含有Wife的引用
    private Wife wife;

    // 构造注入,必须有构造方法
    public Husband(String name, Wife wife) {
        this.name = name;
        this.wife = wife;
    }

    // 增加上name的get方法
    public String getName() {
        return name;
    }
    // toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。
    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName()+
                '}';
    }
}

Wife类:必须含有构造方法

package com.bjpowernode.spring.bean;

public class Wife {
    private String name;
    // 含有Husband的引用
    private Husband husband;

   // 构造注入,必须有构造方法
    public Wife(String name, Husband husband) {
        this.name = name;
        this.husband = husband;
    }

    // 增加上name的get方法
    public String getName() {
        return name;
    }

    // toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。
    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

spring.xml配置

我们知道构造注入是:创建对象和属性赋值时同时进行的;而singleton+set注入Spring能够解决的本质是:实例化对象和对象的属性赋值分为两个阶段来完成的。所以singleton+构造注入的循环依赖,Spring容器是无法解决的!

<?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+构造注入-->
    <bean id="husband" class="com.bjpowernode.spring.bean.Husband">
        <constructor-arg index="0" value="张三"/>
        <constructor-arg index="1" ref="wife"/>
    </bean>

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

进行测试:会报相同的错误

主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程对象属性赋值的过程没有分离开,创建对象和属性赋值时同时进行的。

5. Bean的循环依赖之源码分析

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

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

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

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

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

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

Bean生命周期的管理,参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法,然后在这个方法中,找到addSingletonFactory方法:

点击addSingletonFactory方法进入DefaultSingletonBeanRegistry类,这个类中有三个比较重要的缓存:    

①singletonObjects:一级缓存

②earlySingletonObjects:二级缓存

③singletonFactories:三级缓存

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

private final Map<String, Object> singletonObjects                  一级缓存
private final Map<String, Object> earlySingletonObjects             二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories      三级缓存

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

DefaultSingletonBeanRegistry类中的addSingletonFactory()方法,这个方法的作用是将创建Bean对象的ObjectFactory对象存到三级缓存工厂里,提前曝光!

 在分析一下getSingleton方法,加深理解:

从源码中可以看到:spring会先从一级缓存中获取Bean,如果获取不到;则从二级缓存中获取Bean,如果二级缓存还是获取不到;则从三级缓存中获取之前曝光的ObjectFactory对象,

并把它放到二级缓存里,放到二级缓存以后,就把三级缓存清空了;最终通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。

总结:

①Spring只能解决setter方法注入的单例bean之间的循环依赖。

②ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。

③Spring在创建ClassA对象后,不需要等给属性赋值,直接将其 ”曝光“ 到bean缓存当中。

④在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。

从而解决循环依赖问题!

猜你喜欢

转载自blog.csdn.net/m0_61933976/article/details/128698478
今日推荐