Java面试题:Spring中的循环依赖,给程序员带来的心理阴影
循环依赖通常发生在两个或多个Spring Bean之间,它们通过构造器、字段(使用@Autowired)或setter方法相互依赖,从而形成一个闭环。下面是一个使用字段注入(即使用@Autowired)导致的循环依赖的示例:
示例代码:
假设我们有两个类,ClassA 和 ClassB,它们相互依赖:
public class ClassA {
@Autowired
private ClassB classB;
// ... 其他代码 ...
}
@Component
public class ClassB {
@Autowired
private ClassA classA;
// ... 其他代码 ...
}
在上面的示例中,ClassA 依赖 ClassB,而 ClassB 又依赖 ClassA。当Spring容器启动时,它会尝试为这两个类创建bean的实例。但是,由于它们之间的循环依赖,这会导致问题。

问题说明:
当Spring容器创建ClassA的bean时,它会发现ClassA依赖于ClassB,所以它会尝试创建ClassB的bean。当Spring容器创建ClassB的bean时,它又会发现ClassB依赖于ClassA,但此时ClassA的bean还没有被完全初始化(因为它正在等待ClassB的bean),这就形成了一个死循环。
那么Spring是如何解决这种循环依赖问题的?
三级缓存机制:
Spring容器在创建bean的过程中,会维护三个缓存,分别是:singletonObjects(一级缓存)、earlySingletonObjects(二级缓存)和singletonFactories(三级缓存)。
当Spring容器开始实例化一个bean时,会先将其ObjectFactory(对象工厂)放入三级缓存中。如果在实例化过程中需要注入其他bean,并且这个bean也正在实例化中,Spring会先从一级缓存中查找该bean的实例。如果没有找到,会到二级缓存中查找。如果二级缓存中也没有,那么会到三级缓存中查找ObjectFactory,并调用ObjectFactory的getObject方法来获取bean的实例。
当获取到bean的实例后,会将其放入二级缓存中,并从三级缓存中移除ObjectFactory。
当bean的实例化过程完成后,会将其放入一级缓存中。
通过这种方式,Spring可以在bean的实例化过程中解决循环依赖问题。
@Lazy注解:
在Spring中,可以使用@Lazy注解来延迟bean的初始化。当一个bean被标记为@Lazy时,Spring容器在启动时不会立即实例化它,而是在第一次被使用时才进行实例化。
通过将循环依赖的bean声明为懒加载,可以延迟它们的初始化过程,从而避免在容器启动时发生循环依赖问题。
需要注意的是,@Lazy注解只能用于单例作用域的bean,并且要求依赖项必须是接口类型。
以下是使用@Lazy注解来解决循环依赖的示例代码:
@Component
public class ClassA { private final ClassB classB; // 使用@Autowired和@Lazy注解来延迟ClassB的注入
@Autowired
public ClassA(@Lazy ClassB classB) {
this.classB = classB;
} // ... 其他代码 ...
} @Component
public class ClassB { private final ClassA classA; // 使用@Autowired和@Lazy注解来延迟ClassA的注入
@Autowired
public ClassB(@Lazy ClassA classA) {
this.classA = classA;
} // ... 其他代码 ...
}
在上面的示例中,@Lazy注解被用于ClassA和ClassB的构造器参数上,以延迟它们之间的依赖注入。这意味着在创建ClassA时,它不会立即尝试去初始化ClassB,而是会得到一个代理对象。同样,在创建ClassB时,它也不会立即初始化ClassA。
避免构造器循环依赖:
在Spring中,构造器循环依赖是无法解决的,因为构造器在实例化bean的过程中被调用,如果两个bean相互依赖对方的构造器,那么就会形成死锁。
因此,在设计bean的依赖关系时,应该尽量避免使用构造器注入来创建循环依赖。可以使用setter注入或字段注入来代替构造器注入。
下面是一个构造器循环依赖的错误代码示例:
@Component
public class ClassA { private final ClassB classB; // 通过构造器注入ClassB,形成循环依赖
@Autowired
public ClassA(ClassB classB) {
this.classB = classB;
} // ... 其他代码 ...
} @Component
public class ClassB { private final ClassA classA; // 通过构造器注入ClassA,与ClassA形成循环依赖
@Autowired
public ClassB(ClassA classA) {
this.classA = classA;
} // ... 其他代码 ...
}
在上面的示例中,ClassA和ClassB各自在构造函数中依赖于对方,这就形成了构造器循环依赖。
当Spring容器尝试创建这两个bean的实例时,会遇到问题:
Spring首先尝试创建ClassA的实例,并发现它需要ClassB的实例。
然后,Spring尝试创建ClassB的实例,但发现它需要ClassA的实例。
由于ClassA的实例正在等待ClassB的实例,而ClassB的实例又正在等待ClassA的实例,这导致了一个死循环,无法继续创建bean的实例。
综上所述,Spring通过三级缓存机制、@Lazy注解以及避免构造器循环依赖等方式来解决循环依赖问题。这些机制使得Spring容器能够更加灵活地处理bean之间的依赖关系,提高系统的可维护性和可扩展性。
Java面试题:Spring中的循环依赖,给程序员带来的心理阴影的更多相关文章
- 面试必杀技,讲一讲Spring中的循环依赖
本系列文章: 听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configu ...
- 面试阿里,腾讯,字节跳动90%都会被问到的Spring中的循环依赖
前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...
- 从一部电影史上的趣事了解 Spring 中的循环依赖问题
title: 从一部电影史上的趣事了解 Spring 中的循环依赖问题 date: 2021-03-10 updated: 2021-03-10 categories: Spring tags: Sp ...
- 【Spring】Spring中的循环依赖及解决
什么是循环依赖? 就是A对象依赖了B对象,B对象依赖了A对象. 比如: // A依赖了B class A{ public B b; } // B依赖了A class B{ public A a; } ...
- Spring中的循环依赖解决详解
前言 说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚.本文就试着尽自己所能,对此做出一个较详细的解读.另,需注意一点,下文 ...
- Spring中解决循环依赖报错的问题
什么是循环依赖 当一个ClassA依赖于ClassB,然后ClassB又反过来依赖ClassA,这就形成了一个循环依赖: ClassA -> ClassB -> ClassA 原创声明 本 ...
- 一起来踩踩 Spring 中这个循环依赖的坑
1. 前言 2. 典型场景 3. 什么是依赖 4. 什么是依赖调解 5. 为什么要依赖注入 6. Spring的依赖注入模型 7. 非典型问题 参考资料 1. 前言 这两天工作遇到了一个挺有意思的Sp ...
- Spring中的循环依赖
循环依赖 在使用Spring时,如果主要采用基于构造器的依赖注入方式,则可能会遇到循环依赖的情况,简而言之就是Bean A的构造器依赖于Bean B,Bean B的构造器又依赖于Bean A.在这种情 ...
- Spring 如何解决循环依赖问题?
在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的. 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能 ...
- Spring如何解决循环依赖问题
目录 1. 什么是循环依赖? 2. 怎么检测是否存在循环依赖 3. Spring怎么解决循环依赖 本文主要是分析Spring bean的循环依赖,以及Spring的解决方式. 通过这种解决方式,我们可 ...
随机推荐
- OpenHarmony社区运营报告(2023年7月)
本月快讯 • 2023年7月28日-29日,全球软件质量&效能大会(简称"QECon")圆满举行,OpenAtom OpenHarmony(简称"OpenHar ...
- 直播预告丨OpenHarmony标准系统多媒体子系统之视频解读
5月19日(周四)晚上19点,OpenHarmony开源开发者成长计划知识赋能第五期"掌握OpenHarmony多媒体的框架原理"的第五节直播课,即将开播! 深开鸿资深技术专家胡浩 ...
- C# 布尔值和条件语句:入门指南和实用示例
C# 布尔值 在编程中,通常需要一个只能有两个值之一的数据类型,比如: 是 / 否 开 / 关 真 / 假 为此,C# 有一个 bool 数据类型,可以取 true 或 false 的值. 布尔值 使 ...
- Gitee码云:用git上传本地文件到码云gitee的方法
首先登录码云 https://gitee.com/,注册一个账号,并登录账号. 1. 在码云上创建项目 在码云首页顶部,下图所示,右上角头像旁边的加号,鼠标移上去会显示下拉的,点击"新建项目 ...
- 探索生成式AI的未来:Chat与Agent的较量与融合
近年来,生成式人工智能(AI)不仅在技术界引起了广泛关注,更成为了推动多个行业革新的关键力量.这种技术之所以备受瞩目,不仅在于其独特的创造性和高效性,还在于它对未来商业模式和社会结构可能产生的深远影响 ...
- Qt 操作注册表
一.写入注册表 #include <QSettings> //实例 QSettings //参数 1:注册表位置 //参数 2:操作 windows 注册表 QSettings::Nati ...
- .NET Aspire预览5版本 发布
2024年4月11日发布了.NET Aspire预览5版本,这个版本引入了对AWS的支持,并对Azure功能进行了改进.重点内容包括拆分Aspire.Hosting和Aspire.Hosting.Az ...
- 重新点亮linux 命令树————文件列表查看命令[二]
前言 整理一下文件查看命令 正文 主要是pwd和ls命令 pwd 这个是一个非常常用的命令,在shell脚本中基本都有,表示的是当前目录. 这是一个非常简单,但是非常实用的命令. 通过使用pwd -- ...
- mmcls 多标签模型部署在torch serve
GitHub仓库:gy-7/mmcls_multi_label_torchserve (github.com) 各个文件说明: cls_requests_demo:分类模型请求api服务的demo d ...
- ImageJ软件使用教程(三):目标计数
目录 多点工具法 阀值分割法 二值化 填充分割 自动计数 显示结果 总结 参考资料 本文以钢筋计数为例,讲解一下如何使用ImageJ软件进行计数,这里只介绍两种方法: 多点工具法 阀值分割法 钢筋计数 ...