用AI助手搜索资料,搞定Spring循环依赖面试2026-04-10

小编头像

小编

管理员

发布于:2026年05月10日

53 阅读 · 0 评论

Spring框架的核心考点之一——循环依赖,很多开发者平时用着没问题,一到面试就答不清楚。本文用AI助手搜集了最新的技术资料,系统梳理Spring解决循环依赖的底层原理、三级缓存机制及面试常考要点。

一、痛点切入:为什么需要关注循环依赖

开发中最常见的循环依赖场景如下:

java
复制
下载
@Service

public class OrderService { @Autowired private UserService userService; // OrderService依赖UserService } @Service public class UserService { @Autowired private OrderService orderService; // UserService依赖OrderService,形成闭环 }

在Spring Boot 2.6+版本中,默认已禁止循环依赖,启动时会直接抛出异常-27。即便在允许循环依赖的低版本中,若不做特殊处理,也会抛出BeanCurrentlyInCreationException

旧有代码的典型问题是:Bean之间的依赖关系混乱,修改一处往往会牵连多处,耦合度越来越高,最终难以维护。Spring的三级缓存机制正是为了解决这一问题而设计的-

二、核心概念讲解:什么是循环依赖

循环依赖(Circular Dependency),指两个或多个Bean之间互相持有对方的引用,形成闭环依赖关系-1。典型场景即Bean A依赖Bean B,同时Bean B又依赖Bean A。

生活化类比:A说“我要干活,但我得先拿到B的工具”,B说“我也要干活,但我得先拿到A的工具”。A等B,B等A,谁也动不了,这就卡死了-12

在Spring中,循环依赖分为三种类型-11

  1. 构造器注入循环依赖:创建对象时即需依赖,无法解决,会报异常

  2. 多例模式setter注入循环依赖:每次getBean都会创建新实例,无穷无尽,最终导致OOM

  3. 单例模式setter/字段注入循环依赖唯一能被Spring解决的场景

三、关联概念讲解:Spring的三级缓存

Spring解决循环依赖的核心是三级缓存机制,定义在DefaultSingletonBeanRegistry类中-1

缓存级别名称存储内容作用
一级缓存singletonObjects完全初始化完成的Bean(成品)供业务直接使用
二级缓存earlySingletonObjects提前暴露的半成品Bean(已实例化,未填充属性)存储已确定的早期引用
三级缓存singletonFactoriesObjectFactory对象工厂按需生成代理对象

三级缓存不存对象,而是存一个lambda或匿名内部类形式的ObjectFactory-2,仅在真正需要暴露早期引用时,才动态决定是否生成代理。

获取Bean的缓存读取顺序:一级缓存 → 二级缓存 → 三级缓存-11

四、概念关系与区别总结

三级缓存的核心协作逻辑:一级存成品,二级存已确定的早期引用,三级存“将来可能生成代理”的工厂-2

一句话概括:Spring通过提前暴露正在创建中的Bean引用,让依赖方先拿到一个“半成品”,等双方都完成后,再自动完成最终的依赖注入。

五、代码示例演示

以A依赖B、B依赖A为例,Spring的处理流程如下-49

步骤1:创建A → 将A的ObjectFactory放入三级缓存singletonFactories
步骤2:填充A的依赖B → 触发B的生命周期
步骤3:创建B → 将B的ObjectFactory放入三级缓存
步骤4:填充B的依赖A → 从三级缓存获取A的工厂,生成早期引用,放入二级缓存earlySingletonObjects,并清除三级缓存中A的条目
步骤5:B初始化完成 → 移入一级缓存singletonObjects
步骤6:A继续填充 → 从一级缓存获取B,完成初始化

执行流程可简化为:getBean(A) → 实例化A → 放入三级缓存 → 填充A属性(发现依赖B)→ getBean(B) → 实例化B → 放入三级缓存 → 填充B属性(发现依赖A)→ 从三级缓存获取A工厂 → 生成代理对象 → 放入二级缓存 → B初始化完成 → 放入一级缓存 → A继续填充 → A初始化完成 → 放入一级缓存

六、底层原理/技术支撑

三级缓存机制底层依赖的核心技术包括:

  1. 反射机制:Spring通过反射实例化Bean对象,即便在未完成依赖注入时也能创建对象实例

  2. 代理模式:三级缓存中的ObjectFactory采用了工厂模式设计,将“是否代理”的决定延迟到第一次被引用时-2

  3. singletonsCurrentlyInCreation集合:记录当前正在创建的Bean名称,用于判断是否发生循环依赖-1

为什么需要三级,两级不行吗? 因为Spring要兼顾两个需求:保证循环依赖能解,同时保证AOP代理正确生效。如果只有两级缓存,必须在创建Bean实例后立刻决定是否代理,但此时尚未走到初始化步骤,无法判断是否需要增强(如@PostConstruct方法里才加标记)-2

七、高频面试题与参考答案

Q1:Spring是如何解决循环依赖的?

核心答案是三级缓存机制。Spring在创建Bean时,会提前将实例化后的对象(尚未完成依赖注入)以ObjectFactory的形式放入三级缓存,当检测到循环依赖时,其他Bean可以从三级缓存获取这个早期引用来完成自身创建,从而打破依赖闭环-

Q2:为什么要用三级缓存,两级不行吗?

两级缓存无法同时满足循环依赖解决和AOP代理正确性。若只使用两级缓存,就必须在实例化后立即决定是否生成代理,但此时尚未执行初始化步骤(如@PostConstruct),无法判断是否需要AOP增强。三级缓存将“要不要代理”的判断延迟到第一次被其他Bean引用时,实现了按需代理-2

Q3:构造器注入的循环依赖为什么解决不了?

因为构造器注入要求在实例化阶段就完成所有依赖注入,此时Bean尚未放入三级缓存,Spring无法提前暴露未完成的对象引用,导致循环依赖检测时无法提供早期引用,最终抛出BeanCurrentlyInCreationException-

Q4:如果A依赖B、B依赖A,具体的创建流程是怎样的?

getBean(A) → 实例化A → 放入三级缓存 → 填充A属性(发现依赖B)→ getBean(B) → 实例化B → 放入三级缓存 → 填充B属性(发现依赖A)→ 从三级缓存获取A工厂 → 生成早期引用 → 放入二级缓存 → B初始化完成 → 放入一级缓存 → A继续填充 → A初始化完成 → 放入一级缓存-6

Q5:Spring Boot 2.6+版本中如何处理循环依赖?

Spring Boot 2.6+版本默认禁止循环依赖,启动时会抛出异常。如需解决,可在配置文件中设置spring.main.allow-circular-references=true启用支持,但更推荐使用@Lazy延迟加载或重构代码设计从根本上解决问题-27

八、结尾总结

本文核心知识点回顾:

  • 循环依赖:两个或多个Bean互相引用形成闭环

  • 三级缓存singletonObjects(一级)→ earlySingletonObjects(二级)→ singletonFactories(三级)

  • 核心原理:提前暴露半成品Bean引用,按需生成代理

  • 唯一可解决场景:单例模式 + setter/字段注入

  • 无法解决场景:构造器注入、多例模式注入

易错点提醒

  • 三级缓存不是为了解决所有循环依赖,而是精准支持单例 + 构造器之外注入的场景-2

  • @Lazy注解是解决构造器循环依赖最优雅的方式,通过注入代理对象打破循环-27


本文由AI助手辅助资料完成,数据均基于公开技术文章,力求准确。下一篇将深入讲解Spring AOP的代理机制与三级缓存的配合细节,敬请期待。

标签:

相关阅读