三级缓存与循环依赖

1. 三级缓存的职责划分

  • 一级缓存 singletonObjects:存放完全初始化完成的单例 Bean。
  • 二级缓存 earlySingletonObjects:存放提前暴露的早期引用(可能是原始对象,也可能是代理对象)。
  • 三级缓存 singletonFactories:存放 ObjectFactory,按需生成早期引用。

2. 为什么需要三级缓存

  • 如果只有一级缓存,循环依赖场景下无法提前拿到对象,注入会失败。
  • 如果只有二级缓存(直接放对象),很难优雅支持 AOP 等"可能需要提前生成代理"的场景。
  • 三级缓存通过工厂延迟创建早期引用:既能解决循环依赖,也能在需要时生成代理。

3. 循环依赖如何被解除(A -> B -> A

  • 创建 A 后先把 A 的 ObjectFactory 放入三级缓存。
  • A 依赖 B,转而创建 B;B 依赖 A 时,按顺序查一级、二级、三级。
  • 三级命中后调用工厂拿到 A 的早期引用,并放入二级缓存,再注入给 B。
  • B 完成后回到 A,A 注入 B 并完成初始化,最后进入一级缓存。

4. 动态代理(AOP)介入时发生什么

  • 关键点在 getEarlyBeanReference:当 B 反向依赖 A 时,A 的早期引用可被自动代理创建器包装成代理对象。
  • 因此 B 注入到的 A 往往是代理对象
  • A 自身后续初始化结束后,容器会对齐"最终暴露对象",确保一级缓存中是同一个代理实例,避免原始对象与代理对象不一致。

5. 实现与验证要点(本次补充)

  • 新增了可观测 Advice(计数器)用于验证代理拦截是否实际生效。

  • 新增了循环依赖 + 自动代理 XML 配置及测试。

  • 关键断言:

    • b.getA() == a(注入对象与容器最终对象一致)
    • a 为代理对象(例如 CGLIB 代理类名包含 $$
    • 调用切点方法后 Advice 计数增加。

时序图 1(中文):三级缓存解决循环依赖(无代理)

sequenceDiagram autonumber participant BF as Bean工厂 participant C1 as 一级缓存 participant C2 as 二级缓存 participant C3 as 三级缓存 participant A as BeanA participant B as BeanB BF->>BF: 获取Bean A BF->>A: 实例化A BF->>C3: 放入A的对象工厂 BF->>BF: A填充属性 发现依赖B BF->>BF: 获取Bean B BF->>B: 实例化B BF->>C3: 放入B的对象工厂 BF->>BF: B填充属性 发现依赖A BF->>C1: 查询A C1-->>BF: 未命中 BF->>C2: 查询A C2-->>BF: 未命中 BF->>C3: 查询A的对象工厂 C3-->>BF: 命中对象工厂 BF->>C3: 调用工厂创建A早期引用 BF->>C2: 放入A早期引用 BF->>C3: 删除A对象工厂 BF->>B: 将A早期引用注入B BF->>BF: B初始化完成 BF->>C1: 放入单例B BF->>A: 将B注入A BF->>BF: A初始化完成 BF->>C1: 放入单例A

时序图 2(英文):Three-level Cache Resolves Circular Dependency (No Proxy)

sequenceDiagram autonumber participant BF as BeanFactory participant C1 as Level1Cache participant C2 as Level2Cache participant C3 as Level3Cache participant A as BeanA participant B as BeanB BF->>BF: getBean A BF->>A: instantiate A BF->>C3: put factory for A BF->>BF: populate A, needs B BF->>BF: getBean B BF->>B: instantiate B BF->>C3: put factory for B BF->>BF: populate B, needs A BF->>C1: lookup A C1-->>BF: miss BF->>C2: lookup A C2-->>BF: miss BF->>C3: lookup factory for A C3-->>BF: factory found BF->>C3: call factory to get early A BF->>C2: put early A BF->>C3: remove factory for A BF->>B: inject early A into B BF->>BF: finish B initialization BF->>C1: put singleton B BF->>A: inject B into A BF->>BF: finish A initialization BF->>C1: put singleton A

时序图 3(中文):循环依赖 + 动态代理(A 被代理)

sequenceDiagram autonumber participant BF as Bean工厂 participant C1 as 一级缓存 participant C2 as 二级缓存 participant C3 as 三级缓存 participant APC as 自动代理创建器 participant A0 as A原始对象 participant AP as A代理对象 participant B as BeanB BF->>BF: 获取Bean A BF->>A0: 实例化A原始对象 BF->>C3: 放入A的对象工厂 BF->>BF: A填充属性 发现依赖B BF->>BF: 获取Bean B BF->>B: 实例化B BF->>C3: 放入B的对象工厂 BF->>BF: B填充属性 发现依赖A BF->>C1: 查询A C1-->>BF: 未命中 BF->>C2: 查询A C2-->>BF: 未命中 BF->>C3: 查询A对象工厂 C3-->>BF: 命中对象工厂 BF->>APC: 获取A早期引用 APC-->>BF: 返回A代理对象 BF->>C2: 放入A代理对象 BF->>C3: 删除A对象工厂 BF->>B: 注入A代理对象 BF->>BF: B初始化完成 BF->>C1: 放入单例B BF->>A0: 注入B并完成初始化 BF->>C2: 查询A早期引用 C2-->>BF: 返回A代理对象 BF->>C1: 放入单例A代理对象

时序图 4(英文):Circular Dependency with Dynamic Proxy (A is Proxied)

sequenceDiagram autonumber participant BF as BeanFactory participant C1 as Level1Cache participant C2 as Level2Cache participant C3 as Level3Cache participant APC as AutoProxyCreator participant A0 as A_RawObject participant AP as A_ProxyObject participant B as BeanB BF->>BF: getBean A BF->>A0: instantiate raw A BF->>C3: put factory for A BF->>BF: populate A properties, needs B BF->>BF: getBean B BF->>B: instantiate B BF->>C3: put factory for B BF->>BF: populate B properties, needs A BF->>C1: lookup A C1-->>BF: miss BF->>C2: lookup A C2-->>BF: miss BF->>C3: lookup factory for A C3-->>BF: factory found BF->>APC: get early reference for A APC-->>BF: return proxy A BF->>C2: put proxy A BF->>C3: remove factory for A BF->>B: inject proxy A BF->>BF: finish B initialization BF->>C1: put singleton B BF->>A0: inject B and initialize A BF->>C2: lookup early A reference C2-->>BF: return proxy A BF->>C1: put singleton proxy A
相关推荐
考虑考虑8 小时前
arthas使用
java·运维·后端
李昊哲小课8 小时前
Spring Boot 4 旅游主题实战教程 阶段三:数据持久化
spring boot·后端·mybatis·旅游
AI 编程助手GPT8 小时前
Bun 1.4 正式发布:从 Zig 改写为 Rust,内置浏览器、图片处理和并行测试
开发语言·人工智能·后端·ai·chatgpt
IT_陈寒9 小时前
Redis大KEY删除慢到手抖,这几个方法让我少熬一夜
前端·人工智能·后端
步行cgn9 小时前
传统 SSM 与 Spring Boot 开发对比:从配置地狱到约定优于配置
java·spring boot·后端
JavaGuide11 小时前
一个 SKILL.md 拿下 2.3 万 Star:让 Codex/Claude Code 少说废话、先给答案
前端·后端
陈随易18 小时前
pm2 替代品,nodejs&bun 线上部署必备工具
前端·后端·程序员
丘山一郎19 小时前
Spring 中的IOC控制反转 和DI依赖注入
java·后端·spring
码视野19 小时前
基于 Spring Boot + Vue3 的【大学英语四六级 (CET-4/6) 作文智能评分与句式润色系统】设计与实现(含PRD/三端高保真源码/大屏)
java·前端·人工智能·spring boot·后端·vue3
MetaLite19 小时前
Spring-AOP自调用为什么失效-AopContext真能解决吗
java·后端·spring