java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring解决单例Bean循环依赖

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

作者:长不胖的路人甲

还在为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

创建 B

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

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

回到 A 的属性填充

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

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

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

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

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

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

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循环依赖的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文