Bean的循环依赖问题

2023.11.10

通俗来讲,循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。

正常调用这两对象不会出现问题,但是被 Spring 容器管理后的对象就会出现循环依赖问题。

singleton下的set注入产生的循环依赖

下面先测试一下在singleton+setter的模式下产生的循环依赖,Spring是否能够解决。

定义相互依赖的两个类husband和wife:

java 复制代码
package spring6.bean;

public class husband {
    private String name;
    private wife wife;

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

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

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
java 复制代码
package spring6.bean;

public class wife {
    private String name;
    private husband husband;

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

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

    public String getName() {
        return name;
    }

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

在xml文件配置:

XML 复制代码
    <bean id="husbandBean" class="spring6.bean.husband" scope="singleton">
        <property name="name" value="艾弗森"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="spring6.bean.wife" scope="singleton">
        <property name="name" value="卡戴珊"/>
        <property name="husband" ref="husbandBean"/>
    </bean>

测试程序:

java 复制代码
    @Test
    public void testSingletonAndSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("cycle-depend.xml");
        husband husbandBean = applicationContext.getBean("husbandBean", husband.class);
        wife wifeBean = applicationContext.getBean("wifeBean",wife.class);
        System.out.println(husbandBean);
        System.out.println(wifeBean);
    }

运行结果:

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

根据bean的生命周期可以知道,spring容器当中的bean是先被实例化的 实例化之后才是属性初始化,所以当husband需要给wife属性赋值的时候 wife这个bean已经提前曝光了而且还是单例的,所以可以解决循环依赖问题。

prototype下的set注入产生的循环依赖

再来测试一下prototype+set注入的方式下,循环依赖会不会出现问题,只需要将配置文件的两个singleton都改为prototype即可。 运行测试程序:

发现结果会报:BeanCreationException 异常

再次测试,发现如果只改一个singleton为prototype,也可以正常运行。

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

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

不放代码了,直接说结论:不行。

因为构造方法注入会导致 实例化对象的过程****和 对象属性赋值的过程没有分离开,这两过程是同时进行的,而之前set注入中,两个过程是分离开的,这也是set注入能解决循环依赖的根本原因。

Spring解决循环依赖的机理

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

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

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

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

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

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

Spring的三级缓存

java 复制代码
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
​
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

​

一级缓存:存储的是完整的单例Bean对象,这个Bean对象已经赋值过了。

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

三级缓存:存储的是单例工厂对象,每一个单例Bean对象都会对应一个单例工厂对象。

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

相关推荐
程序员泠零澪回家种桔子10 分钟前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构
CodeCaptain18 分钟前
nacos-2.3.2-OEM与nacos3.1.x的差异分析
java·经验分享·nacos·springcloud
Anastasiozzzz1 小时前
Java Lambda 揭秘:从匿名内部类到底层原理的深度解析
java·开发语言
骇客野人1 小时前
通过脚本推送Docker镜像
java·docker·容器
铁蛋AI编程实战1 小时前
通义千问 3.5 Turbo GGUF 量化版本地部署教程:4G 显存即可运行,数据永不泄露
java·人工智能·python
晚霞的不甘2 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
SunnyDays10112 小时前
使用 Java 冻结 Excel 行和列:完整指南
java·冻结excel行和列
摇滚侠2 小时前
在 SpringBoot 项目中,开发工具使用 IDEA,.idea 目录下的文件需要提交吗
java·spring boot·intellij-idea
云姜.2 小时前
java多态
java·开发语言·c++
李堇2 小时前
android滚动列表VerticalRollingTextView
android·java