Spring相关面试

什么是循环依赖

循环依赖就是A依赖B,然后B又依赖A了,无法确定加载模块顺序

Spring如何解决循环依赖

Spring主要用三级缓存来解决循环依赖

一级缓存:用于存储完全初始化完成的单例Bean

二级缓存:用于存储尚未完全初始化,但已经实例化的Bean

三级缓存:用于存储对象工厂,比如AOP代理对象创建的时候就需要工厂了。

解决步骤就是:

步骤 1:开始创建 Bean A

  • Spring 调用 A 的构造方法,实例化 A 对象(此时 A 的属性还未注入)
  • 将 A 的 ObjectFactory放入三级缓存(singletonFactories)
  • singletonFactories.put("a", () -> getEarlyBeanReference("a", a));

步骤 2:为 A 填充属性(发现依赖 B)

  • Spring 发现 A 需要注入 B
  • 于是转去创建 B

步骤 3:开始创建 Bean B

  • 实例化 B 对象
  • 将 B 的 ObjectFactory 放入三级缓存
  • 为 B 填充属性 → 发现依赖 A

步骤 4:B 尝试获取 A(此时 A 还未创建完!)

  • Spring 先查一级缓存 → 没有
  • 查二级缓存 → 没有
  • 查三级缓存 → 有!
  • 于是调用ObjectFactory.getObject() 获取 A 的早期引用
    • 如果 A 不需要 AOP 代理:直接返回原始 A 对象
    • 如果 A 需要 AOP 代理:通过工厂创建代理对象(这就是三级缓存存在的核心原因!)
    • 将这个早期 A(或代理)放入二级缓存,并从三级缓存中移除 A 的工厂
    • 把这个早期 A 注入给 B

步骤 5:B 继续完成初始化

  • B 的属性填充完成(拿到了 A)
  • 执行 B 的初始化方法(@PostConstructInitializingBean 等)
  • B 完全初始化好后:
    • 放入一级缓存
    • 从二级、三级缓存中清除 B 的条目

步骤 6:回到 A 的创建流程

  • A 拿到已初始化好的 B,完成自己的属性注入
  • 执行 A 的初始化方法
  • A 完全初始化好后:
    • 放入一级缓存
    • 清除 A 在二级/三级缓存中的残留

✅ 最终:A 和 B 都成功创建,且互相持有对方的引用。

Spring 不能解决的情况

场景 是否支持 原因
单例 + setter 注入 ✅ 支持 三级缓存可解决
单例 + 构造器注入 ❌ 不支持 实例化前就要依赖,无法提前暴露引用
prototype 作用域循环依赖 ❌ 不支持 每次都创建新实例,无法缓存
多例(request/session) ❌ 不支持 生命周期不由 Spring 单例容器管理