Spring 的三级缓存,两级够吗

Spring 的三级缓存机制主要是为了解决 循环依赖 问题,同时保证 AOP 代理 的正确性。

结论是:两级缓存不够,必须用三级缓存


一、Spring 三级缓存分别是什么

  • 一级缓存singletonObjects(已经完成初始化的单例 Bean)
  • 二级缓存earlySingletonObjects(提前暴露的 Bean,尚未完全初始化,但已可被引用)
  • 三级缓存singletonFactories(对象工厂 ObjectFactory,用于生成 Bean 的早期引用,通常是为了生成 AOP 代理)

二、为什么两级不够

1. 如果没有 AOP

理论上,仅为了解决循环依赖,二级缓存是足够的:

  • A 创建时,实例化后放到二级缓存(或通过工厂暴露早期对象)
  • B 依赖 A,从缓存拿到未完全初始化的 A
  • B 完成初始化后,A 继续完成初始化

但问题出现在 A 需要被 AOP 代理 时。


2. 有 AOP 时的代理对象生成时机

Spring AOP 通常是在 Bean 初始化后postProcessAfterInitialization)通过后置处理器生成代理对象。

但在循环依赖中:

  • 如果 B 依赖 A,A 必须提前暴露给 B
  • 如果 A 最终要的是一个 代理对象,那么 B 依赖的也必须是同一个代理对象,而不是原始对象
  • 若只用二级缓存,在 A 实例化后直接暴露原始对象,之后 A 初始化完成时生成代理,B 里持有的还是原始对象,就会出现 对象不一致 的问题

三、三级缓存如何解决

一、正常 Bean 的生命周期(无循环依赖)

  1. 实例化createBeanInstance) → 原始对象
  2. 属性填充populateBean) → 依赖注入
  3. 初始化initializeBean):
    • 调用 applyBeanPostProcessorsBeforeInitialization
    • 调用 init-method
    • 调用 applyBeanPostProcessorsAfterInitialization
      这一步中,AOP 后置处理器 会检查 Bean 是否需要代理,如果需要则生成代理对象,并返回代理。
  4. 最终放入一级缓存singletonObjects

正常情况下,AOP 代理确实是在初始化完成后才创建的。


二、循环依赖时的特殊流程(A 需要代理,B 依赖 A)

假设 A 需要 AOP,B 依赖 A。

  1. A 实例化 :创建原始对象 A_raw
  2. 提前暴露addSingletonFactoryA_raw 包装成一个 ObjectFactory 放入三级缓存。
    这个工厂的 getObject 方法内部会调用 getEarlyBeanReference(beanName, mbd, A_raw)
  3. A 属性填充:发现需要 B → 去获取 B。
  4. B 开始创建 :实例化 B → 属性填充时发现需要 A → 从缓存获取 A。
    • 一级缓存无(A 未完成)
    • 二级缓存无
    • 三级缓存有工厂 → 调用工厂 ,即执行 getEarlyBeanReference
  5. getEarlyBeanReference 的作用
    它会遍历所有 SmartInstantiationAwareBeanPostProcessor(AOP 后置处理器实现了该接口),并调用其 getEarlyBeanReference 方法。
    此时,AOP 后置处理器发现 A_raw 需要被代理(比如有 @Transactional@Aspect),就直接生成代理对象 A_proxy 并返回
  6. 返回的代理对象
    • 存入二级缓存 earlySingletonObjects(key=beanName, value=代理对象)
    • 从三级缓存移除工厂
  7. B 获取到 A_proxy,完成 B 的创建。
  8. A 继续初始化
    A 在完成属性填充后,进入初始化阶段。但在 applyBeanPostProcessorsAfterInitialization 中,AOP 后置处理器会检查该 Bean 是否已经存在代理(通过一个缓存标记),发现代理已经提前生成,就不会再生成新的代理,直接返回现有的代理对象。
    最终 A 的代理对象被放入一级缓存。

为什么代理可以提前生成?

这是因为 SmartInstantiationAwareBeanPostProcessor 接口专门定义了一个 getEarlyBeanReference 方法,允许后置处理器在 Bean 实例化后、属性填充前 就提供一个对象引用(通常是代理)。

Spring AOP 的 AbstractAutoProxyCreator 实现了这个方法,它会在循环依赖发生时,提前创建代理,而不是等到初始化之后。

所以代理的创建时机有两个可能路径

  • 正常路径 :在 postProcessAfterInitialization 中创建。
  • 提前路径 :在 getEarlyBeanReference 中创建(仅当发生循环依赖且需要提前暴露时)。

这样设计就是为了保证:即使循环依赖,依赖方拿到的也是最终要用的代理对象,而不是原始对象。


四、两级缓存能否模拟三级

如果强行用两级缓存,有两个方案,但都有问题:

  1. 实例化后立刻生成代理

    会破坏 AOP 的正常顺序,有些后置处理可能依赖 Bean 初始化完成后的状态,提前代理会导致某些增强无法正确应用。

  2. 暴露时用原始对象,初始化后再替换

    需要额外维护引用关系并更新所有依赖方,复杂度极高且容易出错。

三级缓存本质是 将"提前暴露"与"生成最终对象"解耦 ,通过 ObjectFactory 延迟决定早期引用是原始对象还是代理对象,兼顾了循环依赖和 AOP。


=

相关推荐
摇滚侠14 分钟前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY37 分钟前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克31 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠2 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌2 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
Agent产品评测局2 小时前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
阿丰资源2 小时前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
呱牛do it3 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
消失的旧时光-19433 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解
NE_STOP4 小时前
Redis--发布订阅命令和Redis事务
java