news 2026/6/9 6:46:21

(19)Bean的循环依赖问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
(19)Bean的循环依赖问题

什么是Bean的循环依赖

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

packagecom.powernode.spring6.bean;/** * @author 动力节点 * @version 1.0 * @className Husband * @since 1.0 **/publicclassHusband{privateStringname;privateWifewife;}
packagecom.powernode.spring6.bean;/** * @author 动力节点 * @version 1.0 * @className Wife * @since 1.0 **/publicclassWife{privateStringname;privateHusbandhusband;}

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

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

packagecom.powernode.spring6.bean;/** * @author 动力节点 * @version 1.0 * @className Husband * @since 1.0 **/publicclassHusband{privateStringname;privateWifewife;publicvoidsetName(Stringname){this.name=name;}publicStringgetName(){returnname;}publicvoidsetWife(Wifewife){this.wife=wife;}// toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。@OverridepublicStringtoString(){return"Husband{"+"name='"+name+'\''+", wife="+wife.getName()+'}';}}
packagecom.powernode.spring6.bean;/** * @author 动力节点 * @version 1.0 * @className Wife * @since 1.0 **/publicclassWife{privateStringname;privateHusbandhusband;publicvoidsetName(Stringname){this.name=name;}publicStringgetName(){returnname;}publicvoidsetHusband(Husbandhusband){this.husband=husband;}// toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。@OverridepublicStringtoString(){return"Wife{"+"name='"+name+'\''+", husband="+husband.getName()+'}';}}
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="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"><beanid="husbandBean"class="com.powernode.spring6.bean.Husband"scope="singleton"><propertyname="name"value="张三"/><propertyname="wife"ref="wifeBean"/></bean><beanid="wifeBean"class="com.powernode.spring6.bean.Wife"scope="singleton"><propertyname="name"value="小花"/><propertyname="husband"ref="husbandBean"/></bean></beans>
packagecom.powernode.spring6.test;importcom.powernode.spring6.bean.Husband;importcom.powernode.spring6.bean.Wife;importorg.junit.Test;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;/** * @author 动力节点 * @version 1.0 * @className CircularDependencyTest * @since 1.0 **/publicclassCircularDependencyTest{@TestpublicvoidtestSingletonAndSet(){ApplicationContextapplicationContext=newClassPathXmlApplicationContext("spring.xml");HusbandhusbandBean=applicationContext.getBean("husbandBean",Husband.class);WifewifeBean=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的实例,直接从缓存中获取即可。从而解决循环依赖问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 21:05:14

32、深入掌握 Bash 条件测试与流程控制

深入掌握 Bash 条件测试与流程控制 1. 使用 test 命令 在 Bash 脚本中,与 if 语句搭配最频繁使用的命令当属 test 。 test 命令可进行各类检查与比较,它有两种等价形式: - test expression - [ expression ] 其中, expression 是一个可被评估为真或假的表…

作者头像 李华
网站建设 2026/6/9 14:01:23

YashanDB数据库的核心优势与应用场景分析

YashanDB是一种新兴的数据库解决方案&#xff0c;具有多种核心优势和应用场景。以下是对其核心优势及应用场景的分析&#xff1a;核心优势1. 高性能- YashanDB采用高效的数据存储和检索机制&#xff0c;能够在处理大规模数据时保持优异的性能&#xff0c;适合对实时性要求较高的…

作者头像 李华
网站建设 2026/6/9 21:35:18

YashanDB数据库的缓存机制及性能提升策略探究

YashanDB数据库的缓存机制及性能提升策略是一个重要的话题&#xff0c;尤其是在面对现代应用对高性能和低延迟的需求时。以下是关于YashanDB的一些缓存机制及其性能提升策略的探讨。一、缓存机制1. 内存缓存&#xff1a;- YashanDB可能使用内存作为主要的数据缓存层&#xff0c…

作者头像 李华
网站建设 2026/6/3 18:32:49

20、Swerve详细设计解析

Swerve详细设计解析 1. 连接与I/O操作 在进行网络连接操作时,连接对象可能会持续一段时间,并且可能会有进一步向连接写入数据的尝试。因此,所有的I/O函数在执行之前都会检查套接字是否仍然打开,以及是否没有出现中止条件。 当向套接字发送数据时,存在部分写入的风险。为…

作者头像 李华
网站建设 2026/6/8 13:48:34

21、节点系统的详细设计与实现

节点系统的详细设计与实现 在节点系统的设计中,存在诸多关键的技术点和实现细节,下面将详细介绍节点系统的设计与实现,包括通用节点和目录节点处理程序等方面。 1. 节点创建的依赖处理 在节点创建过程中,为了避免模块之间的循环依赖问题,采用了将工厂的创建函数传递给目…

作者头像 李华
网站建设 2026/6/8 21:04:35

22、服务器模块详细设计解析

服务器模块详细设计解析 1. 目录操作与 HTML 构建 目录列表的获取需要从文件描述符读取,这意味着它必须经过开放文件管理器,并且可能会因超时被中止。而 HTML 的构建则是使用 TextFrag 模块进行的复杂文本格式化操作。代码假设服务器中有一个 /icons 的 URL 路径用于获…

作者头像 李华