Bean的循环依赖问题全面解析

 更新时间:2025年12月16日 09:23:59   作者:SadSunset  
文章介绍了Spring框架如何解决setter方法注入的单例bean之间的循环依赖问题,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

什么是Bean的循环依赖

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

package com.powernode.spring6.bean;
/**
 * @author 动力节点
 * @version 1.0
 * @className Husband
 * @since 1.0
 **/
public class Husband {
    private String name;
    private Wife wife;
}
package com.powernode.spring6.bean;
/**
 * @author 动力节点
 * @version 1.0
 * @className Wife
 * @since 1.0
 **/
public class Wife {
    private String name;
    private Husband husband;
}

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

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

package com.powernode.spring6.bean;
/**
 * @author 动力节点
 * @version 1.0
 * @className Husband
 * @since 1.0
 **/
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;
    }
    // toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。
    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
package com.powernode.spring6.bean;
/**
 * @author 动力节点
 * @version 1.0
 * @className Wife
 * @since 1.0
 **/
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;
    }
    // toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。
    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}
<?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="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>
    <bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
        <property name="name" value="小花"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
</beans>
package com.powernode.spring6.test;
import com.powernode.spring6.bean.Husband;
import com.powernode.spring6.bean.Wife;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * @author 动力节点
 * @version 1.0
 * @className CircularDependencyTest
 * @since 1.0
 **/
public class CircularDependencyTest {
    @Test
    public void testSingletonAndSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.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可以解决这个问题。

Spring解决循环依赖的机理

Spring为什么可以解决set + singleton模式下循环依赖?
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。
给Bean属性赋值的时候:调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
那么在Spring框架底层源码级别上是如何实现的呢?请看:

在以上类中包含三个重要的属性:
**Cache of singleton objects: bean name to bean instance. **单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】
**Cache of early singleton objects: bean name to bean instance. **早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象【二级缓存】
**Cache of singleton factories: bean name to ObjectFactory. **单例工厂缓存:key存储bean名称,value存储该Bean对应的ObjectFactory对象【三级缓存】
这三个缓存其实本质上是三个Map集合。
我们再来看,在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光。

再分析下面的源码:

从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。
总结:
Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。

到此这篇关于Bean的循环依赖问题的文章就介绍到这了,更多相关Bean循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java的Object类九个方法技巧

    Java的Object类九个方法技巧

    这篇文章主要介绍了Java的Object类九个方法技巧,Java的Object 类的完整路径是java.lang.Object ,是所有类的父类编译,下文相关资料,需要的朋友可以参考一下
    2022-04-04
  • mybatis-plus自定义业务实现方式

    mybatis-plus自定义业务实现方式

    本文介绍了使用MyBatis-Plus(简称MP)进行数据库操作的实现步骤,包括创建实体类、Mapper接口和XML文件、Service接口和实现类,以及测试类,重点强调了业务层参数校验、事务管理以及复杂SQL的处理方式
    2026-03-03
  • MyBatis 参数绑定的具体实现

    MyBatis 参数绑定的具体实现

    本文主要介绍了MyBatis 参数绑定的具体实现,包括默认参数名、@Param注解和POJO/DTO对象三种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-01-01
  • 解决Kibana报错[security_exception] current license is non-compliant for [security]问题

    解决Kibana报错[security_exception] current license&nbs

    ElasticSearch 5.1.1因安全许可证过期报错,解决方法:官网申请新许可证,下载JSON文件并上传至服务器,执行代码时切换root权限,配置host为服务器IP,端口9200,替换原license文件
    2025-07-07
  • SpringBoot多环境切换的灵活配置详细教程

    SpringBoot多环境切换的灵活配置详细教程

    在真实项目开发的时候,一定会有多个环境,下面这篇文章主要给大家介绍了关于SpringBoot多环境切换灵活配置的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-04-04
  • SpringBoot读取Resource下文件的4种方法

    SpringBoot读取Resource下文件的4种方法

    这篇文章主要介绍了SpringBoot读取Resource下文件的4种方法小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Spring MVC实现的登录拦截器代码分享

    Spring MVC实现的登录拦截器代码分享

    这篇文章主要介绍了Spring MVC实现的登录拦截器代码分享,涉及拦截器的简单介绍,拦截器和过滤器的区以及拦截器实现代码等相关内容,这里分享给大家,供需要的朋友参考。
    2017-10-10
  • Springboot的application.properties或application.yml环境的指定运行与配置方式

    Springboot的application.properties或application.yml环境的指定运行与配置方

    本文主要介绍了Spring Boot中配置文件的多种使用方式,包括配置文件的命名、激活、路径指定以及优先级,并结合示例进行了详细说明
    2025-11-11
  • 详解Java异常处理中throw与throws关键字的用法区别

    详解Java异常处理中throw与throws关键字的用法区别

    这篇文章主要介绍了详解Java异常处理中throw与throws关键字的用法区别,这也是Java面试题目中的常客,需要的朋友可以参考下
    2015-11-11
  • Java使用TCP实现在线聊天的示例代码

    Java使用TCP实现在线聊天的示例代码

    这篇文章主要介绍了Java使用TCP实现在线聊天的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01

最新评论