一文详解Spring如何解决单例Bean的循环依赖

 更新时间:2026年07月28日 09:03:02   作者:长不胖的路人甲  
还在为Spring循环依赖头疼?本文用大白话讲清什么是循环依赖、为什么构造器注入会报错,以及三级缓存如何巧妙破解死锁,结合单例Bean创建流程,让你彻底明白AOP代理和半成品暴露的核心设计,面试再也不慌,需要的朋友可以参考下

一、什么是循环依赖

循环依赖:两个或多个单例 Bean 互相持有对方作为依赖,创建时形成死锁,无法完成实例化。

两种典型场景

1. 双向依赖(最常见)

A 里面依赖 B,B 里面依赖 A

@Component
public class A {
    @Autowired
    private B b;
}
@Component
public class B {
    @Autowired
    private A a;
}

2. 多向循环

A→B,B→C,C→A

补充:三种注入方式对循环依赖的支持

  1. setter / 字段注入(@Autowired 成员变量):Spring 可以解决单例循环依赖
  2. 构造器注入无法解决,直接抛 BeanCurrentlyInCreationException
  3. prototype 多例 Bean:完全不支持循环依赖,每次新建对象,缓存无效

二、Spring 解决单例循环依赖核心原理:三级缓存

Spring 通过 DefaultSingletonBeanRegistry 维护三个 Map 缓存,提前暴露半成品对象解决循环。

三级缓存定义

// 一级缓存:存放完全初始化好的成品单例 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 二级缓存:存放实例化完成,但还未填充属性、未初始化的半成品 Bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>();
// 三级缓存:存放 Bean 的工厂 ObjectFactory,用于生成半成品 Bean(支持AOP代理)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

各级缓存作用

  1. 一级缓存 singletonObjects 完整创建、属性填充、初始化完毕的 Bean,对外正常使用。
  2. 三级缓存 singletonFactories(关键) Bean 刚实例化(new 对象)后,立刻把生成当前 Bean 的工厂存入三级缓存。 作用:如果该 Bean 需要 AOP 动态代理,ObjectFactory 会返回代理对象;无 AOP 则返回原生半成品。
  3. 二级缓存 earlySingletonObjects 当出现循环依赖,其他 Bean 需要引用半成品时: 从三级缓存取出 ObjectFactory,生成半成品 / 代理对象,放入二级缓存,并删除三级缓存对应记录。 避免每次都执行代理逻辑,提升性能。

缓存查找顺序

获取单例 Bean 时顺序: 一级缓存 → 二级缓存 → 三级缓存

  1. 一级有:直接返回成品
  2. 一级无、二级有:返回半成品
  3. 一二都无、三级有:工厂创建半成品存入二级,返回

三、分步演示 A、B 循环依赖完整流程(字段注入)

前提:A、B 都是单例,使用 @Autowired 字段注入

创建 A

  • 先判断一级缓存无 A,标记 A 为「正在创建」
  • 反射 new A 对象(实例化完成,属性为空,半成品)
  • 将 A 的 ObjectFactory 放入三级缓存 singletonFactories
  • 开始填充 A 的属性,发现依赖 B,去获取 B

创建 B

  • 一级缓存无 B,标记 B 正在创建
  • new B 半成品,将 B 的工厂存入三级缓存
  • 填充 B 的属性,发现依赖 A,去获取 A

B 获取依赖 A(核心解决点)

  • 一级缓存无 A,二级缓存无 A
  • 三级缓存存在 A 的 ObjectFactory,取出工厂生成 A 半成品
  • 将 A 半成品放入二级缓存 earlySingletonObjects,删除三级缓存的 A
  • 将半成品 A 注入给 B 的属性

B 填充完成,执行初始化(afterPropertiesSet、init-method)

  • 完全初始化后的成品 B 放入一级缓存 singletonObjects
  • 删除二、三级缓存中 B 的记录

回到 A 的属性填充

  • 现在可以拿到完整的成品 B,注入 A 的属性
  • A 完成初始化,成品 A 放入一级缓存,删除二三级缓存 A

整个流程走完,A、B 全部创建完成,循环依赖解除。

四、关键补充高频面试考点

1. 为什么构造器注入无法解决循环依赖?

三级缓存存入工厂的时机是实例化(new 对象)之后。 构造器注入需要在 new 对象阶段就传入依赖,此时对象还没创建,无法提前存入三级缓存暴露半成品,直接抛出异常。

2. 为什么需要三级缓存,只用二级行不行?

不行,三级缓存是为了AOP 动态代理

  • 如果 Bean 需要代理,ObjectFactory 内部会执行 getEarlyBeanReference() 创建代理对象;
  • 若只有二级缓存,提前暴露的只能是原生对象,后续无法替换成代理对象,会导致注入原生对象,事务、切面失效。

3. prototype 多例 Bean 为什么不能解决循环依赖?

多例 Bean 不会存入任何一级缓存,每次获取都新建对象,没有半成品缓存复用机制,循环依赖直接无限创建对象报错。

4. 开发如何避免循环依赖?

  1. 业务分层解耦,调整依赖关系,不要互相依赖;
  2. 使用构造器注入,循环依赖会直接报错,提前发现问题;
  3. 使用 @Lazy 懒加载依赖,注入代理对象,延迟创建目标 Bean。

五、总结

  1. 循环依赖:多个单例 Bean 互相依赖,创建闭环;
  2. Spring 仅支持 单例 + setter / 字段注入 的循环依赖;构造器、多例不支持;
  3. 核心方案:三级缓存提前暴露半成品 Bean
    • 一级:完整成品 Bean
    • 二级:循环依赖临时半成品
    • 三级:Bean 工厂,支持 AOP 代理生成;
  4. 流程核心:实例化后先放入三级缓存,填充属性时若出现循环,取出半成品注入对方,完成创建。

以上就是一文详解Spring如何解决单例Bean的循环依赖的详细内容,更多关于Spring解决单例Bean循环依赖的资料请关注脚本之家其它相关文章!

相关文章

  • 详细分析JAVA8新特性 Base64

    详细分析JAVA8新特性 Base64

    这篇文章主要介绍了JAVA8新特性 Base64的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-08-08
  • 如何使用Java 8 中的 Stream 遍历树形结构

    如何使用Java 8 中的 Stream 遍历树形结构

    这篇文章主要介绍了使用Java 8中的Stream遍历树形结构,我们可以使用Java8中的Stream流一次性把数据查出来,然后通过流式处理,我们一起来看看,代码实现为了实现简单,就模拟查看数据库所有数据到List里面,需要的朋友可以参考下
    2023-08-08
  • spring boot jar的启动原理解析

    spring boot jar的启动原理解析

    这篇文章主要介绍了 spring boot jar的启动原理分析,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2018-02-02
  • Springboot 接口对接文件及对象的操作方法

    Springboot 接口对接文件及对象的操作方法

    这篇文章主要介绍了Springboot 接口对接文件及对象的操作,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • spring boot整合mongo查询converter异常排查记录

    spring boot整合mongo查询converter异常排查记录

    这篇文章主要为大家介绍了spring boot整合mongo查询时抛出converter异常的排查解决记录,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2022-03-03
  • java实现响应重定向发送post请求操作示例

    java实现响应重定向发送post请求操作示例

    这篇文章主要介绍了java实现响应重定向发送post请求操作,结合实例形式分析了java请求响应、重定向及数据处理相关操作技巧,需要的朋友可以参考下
    2020-04-04
  • 浅谈一下RabbitMQ、Kafka和RocketMQ消息中间件对比

    浅谈一下RabbitMQ、Kafka和RocketMQ消息中间件对比

    这篇文章主要介绍了浅谈一下RabbitMQ、Kafka和RocketMQ消息中间件对比,消息中间件属于分布式系统中一个字系统,关注于数据的发送和接收,利用高效可靠的异步信息传递机制对分布式系统中的其余各个子系统进行集成,需要的朋友可以参考下
    2023-05-05
  • Javaweb开发中通过Servlet生成验证码图片

    Javaweb开发中通过Servlet生成验证码图片

    这篇文章主要为大家详细介绍了Javaweb开发中通过Servlet生成验证码图片的相关资料,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • 从源码角度看spring mvc的请求处理过程

    从源码角度看spring mvc的请求处理过程

    这篇文章主要介绍了从源码角度看spring mvc的请求处理过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下
    2019-06-06
  • 如何在Spring中自定义scope的方法示例

    如何在Spring中自定义scope的方法示例

    这篇文章主要介绍了如何在Spring中自定义scope的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-02-02

最新评论