Skip to content

01.3 — 循环依赖与三级缓存

定位: Spring 如何用三级缓存解决循环依赖——以及为什么不用二级 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: 三级缓存机制、延迟代理创建、构造器注入循环依赖

一、这是什么?为什么需要它?

循环依赖:A 依赖 B,B 又依赖 A。如果不做特殊处理,容器会陷入"创建 A -> 需要 B -> 创建 B -> 需要 A -> ..."的死循环。

Spring 通过三级缓存机制,在 Bean 未完成初始化时提前暴露半成品引用,打破循环。

二、原理拆解

2.1 三级缓存数据结构

java
// DefaultSingletonBeanRegistry 中的三个缓存
// 1. 一级缓存 — 完全初始化好的 Bean
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 2. 二级缓存 — 提前暴露的半成品 Bean(尚未完成属性赋值和初始化)
Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 3. 三级缓存 — ObjectFactory 工厂
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

2.2 三级缓存工作流程图

Bean A 开始创建
    -> A 实例化
    -> A 放入三级缓存 (singletonFactories)
    -> A 注入依赖 B
    -> Bean B 是否已创建? -> 否 -> 创建 Bean B
        -> B 实例化
        -> B 放入三级缓存
        -> B 注入依赖 A
            -> 从三级缓存获取 A 的 ObjectFactory
            -> 调用 ObjectFactory -> 生成 A 的早期引用
            -> 移除三级缓存 -> 放入二级缓存
        -> 将早期引用注入 B
        -> B 完成初始化 -> B 移入一级缓存
    -> A 从二级缓存获取完整 B
    -> A 完成初始化 -> A 移入一级缓存

三、关键问题:为什么不是二级缓存?

方案问题
只用两级缓存如果直接用二级缓存存"半成品 Bean",当 AOP 需要创建代理时,必须在放入二级缓存前就创建代理。这导致所有 Bean 实例化后就创建代理,即使没有循环依赖也白白创建
三级缓存ObjectFactory 是延迟代理创建的关键。只有 Bean 真的被循环依赖引用时,才会触发工厂调用创建代理对象。无循环依赖则全程不创建代理

核心结论:三级缓存解决了"延迟代理创建"的问题——让普通 Bean 在最终阶段(postProcessAfterInitialization)才创建代理,仅当发生循环依赖时才提前创建。

局限

  • 仅解决 singleton + 非构造器注入 的循环依赖
  • 构造器注入 的循环依赖无法解决(实例化前就需要依赖)
  • prototype 作用域的循环依赖也无法解决

2.3 @Lazy 代理原理

java
@Component
@Lazy  // 延迟初始化
public class ExpensiveBean { ... }

@Component
public class Client {
    @Autowired
    @Lazy  // 为这个依赖生成懒加载代理
    private ExpensiveBean expensiveBean;
}

工作原理

  1. Spring 为 @Lazy 标注的依赖创建一个 CGLIB 代理对象
  2. 代理对象注入到目标 Bean 中
  3. 首次调用代理对象的方法时,才真正去容器中查找/创建目标 Bean

四、实战验证

java
// 验证三级缓存
// 运行以下配置观察三级缓存行为
@Configuration
public class CircularConfig {
    @Bean
    public DependencyA dependencyA() { return new DependencyA(dependencyB()); }
    @Bean
    public DependencyB dependencyB() { return new DependencyB(dependencyA()); }  
    // singleton + 构造器注入 -> BeanCurrentlyInCreationException!
}

五、面试视角

追问答案要点
Spring 如何解决循环依赖?三级缓存机制,提前暴露半成品 Bean 的 ObjectFactory
为什么不用二级缓存?二级缓存无法实现"延迟代理创建",所有 Bean 实例化后必须立即创建代理
构造器注入的循环依赖能解决吗?不能,因为实例化前就需要依赖,抛 BeanCurrentlyInCreationException
prototype 的循环依赖怎么办?无法解决,Spring 不缓存 prototype Bean
@Lazy 的实现原理?CGLIB 生成代理对象,首次调用才真正创建目标 Bean

📚 相关链接

  • **Bean生命周期** — Bean 的完整生命周期中,循环依赖发生在属性赋值阶段
  • ← 返回 **返回 IoC 容器索引**

Knowledge4J — Java 知识库