什么是循环依赖?Spring 如何解决单例 Bean 的循环依赖?

📅 2026/7/28 5:44:40
什么是循环依赖?Spring 如何解决单例 Bean 的循环依赖?
一、什么是循环依赖循环依赖两个或多个单例 Bean 互相持有对方作为依赖创建时形成死锁无法完成实例化。两种典型场景1. 双向依赖最常见A 里面依赖 BB 里面依赖 Ajava运行Component public class A { Autowired private B b; } Component public class B { Autowired private A a; }2. 多向循环A→BB→CC→A补充三种注入方式对循环依赖的支持setter / 字段注入Autowired 成员变量Spring 可以解决单例循环依赖构造器注入无法解决直接抛BeanCurrentlyInCreationExceptionprototype 多例 Bean完全不支持循环依赖每次新建对象缓存无效二、Spring 解决单例循环依赖核心原理三级缓存Spring 通过DefaultSingletonBeanRegistry维护三个 Map 缓存提前暴露半成品对象解决循环。三级缓存定义java运行// 一级缓存存放完全初始化好的成品单例 Bean private final MapString, Object singletonObjects new ConcurrentHashMap(); // 二级缓存存放实例化完成但还未填充属性、未初始化的半成品 Bean private final MapString, Object earlySingletonObjects new HashMap(); // 三级缓存存放 Bean 的工厂 ObjectFactory用于生成半成品 Bean支持AOP代理 private final MapString, ObjectFactory? singletonFactories new HashMap();各级缓存作用一级缓存 singletonObjects完整创建、属性填充、初始化完毕的 Bean对外正常使用。三级缓存 singletonFactories关键Bean 刚实例化new 对象后立刻把生成当前 Bean 的工厂存入三级缓存。 作用如果该 Bean 需要 AOP 动态代理ObjectFactory 会返回代理对象无 AOP 则返回原生半成品。二级缓存 earlySingletonObjects当出现循环依赖其他 Bean 需要引用半成品时 从三级缓存取出 ObjectFactory生成半成品 / 代理对象放入二级缓存并删除三级缓存对应记录。 避免每次都执行代理逻辑提升性能。缓存查找顺序获取单例 Bean 时顺序一级缓存 → 二级缓存 → 三级缓存一级有直接返回成品一级无、二级有返回半成品一二都无、三级有工厂创建半成品存入二级返回三、分步演示 A、B 循环依赖完整流程字段注入前提A、B 都是单例使用 Autowired 字段注入创建 A先判断一级缓存无 A标记 A 为「正在创建」反射 new A 对象实例化完成属性为空半成品将 A 的 ObjectFactory 放入三级缓存 singletonFactories开始填充 A 的属性发现依赖 B去获取 B创建 B一级缓存无 B标记 B 正在创建new B 半成品将 B 的工厂存入三级缓存填充 B 的属性发现依赖 A去获取 AB 获取依赖 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. 开发如何避免循环依赖业务分层解耦调整依赖关系不要互相依赖使用构造器注入循环依赖会直接报错提前发现问题使用Lazy懒加载依赖注入代理对象延迟创建目标 Bean。五、总结循环依赖多个单例 Bean 互相依赖创建闭环Spring 仅支持单例 setter / 字段注入的循环依赖构造器、多例不支持核心方案三级缓存提前暴露半成品 Bean一级完整成品 Bean二级循环依赖临时半成品三级Bean 工厂支持 AOP 代理生成流程核心实例化后先放入三级缓存填充属性时若出现循环取出半成品注入对方完成创建。