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实例,这样就解决了循环依赖的问题。

相关推荐
22:30Plane-Moon9 分钟前
初识SpringBoot
java·spring boot·后端
黄昏晓x43 分钟前
数据结构----排序
java·数据结构·排序算法
97zz1 小时前
项目配置文件正确但是启动失败,报配置文件内容错误或中间件地址与实际不符
java·中间件·springboot
小醉你真好2 小时前
Spring Boot + ShardingSphere 分库分表实战
java·spring boot·后端·mysql
Vdeilae2 小时前
IIS 让asp.net core 项目一直运行
java·服务器·asp.net
YY_TJJ3 小时前
8.4 Java Web(Maven P50-P57)
java·开发语言·maven
cc蒲公英3 小时前
uniapp x swiper/image组件mode=“aspectFit“ 图片有的闪现后黑屏
java·前端·uni-app
刘大辉在路上3 小时前
IntelliJ IDEA开发编辑器摸鱼看股票数据
java·编辑器·intellij-idea
知其然亦知其所以然3 小时前
ChatGPT太贵?教你用Spring AI在本地白嫖聊天模型!
后端·spring·ai编程
我来整一篇3 小时前
[mysql] 深分页优化
java·数据库·mysql