Spring 三级缓存三个小问题记录

bean的流程

三级缓存‌存储:

存储的是工厂对象(ObjectFactory),用于生成代理对象或普通 Bean 的早期引用。

工厂的 getObject() 方法会调用后置处理器生成代理对象,或直接返回普通 Bean 的早期引用。

‌二级缓存‌存储:

存储的是早期引用,可能是未完成初始化的普通 Bean 或代理对象的早期引用。

二级缓存仅在有循环依赖时参与。

‌一级缓存‌存储:

存储的是完全初始化好的 Bean,包括代理对象和普通 Bean。

‌用户创建的 Bean 的路径‌。

bean依赖创建:

有循环依赖必定会创建代理。

普通 Bean(无循环依赖无代理)‌:

Bean 实例化后,工厂对象放入三级缓存(但无循环依赖时不会实际使用)。

初始化完成后,直接将完全初始化的 Bean 放入一级缓存。

‌路径‌:三级缓存(工厂)→ 一级缓存(但三级缓存的工厂未实际使用)。

有代理的Bean‌:

‌路径 1‌(‌有循环依赖有代理):三级缓存工厂生成早期引用 → 二级缓存 → 一级缓存。 --这里生成的也有可能

‌路径 2‌ (无循环依赖有代理):正常初始化(无循环依赖)时,通过后置处理器生成代理对象 → 一级缓存。


问题一:三级缓存的作用与普通 Bean 为何经过三级缓存

三级缓存的核心作用

java 复制代码
// 三级缓存定义
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
  1. 延迟决策

    • 在 Bean 实例化后立即加入三级缓存 ,此时:
      • 尚未进行属性注入
      • 未执行初始化方法
      • 无法确定是否需要代理(代理决策在初始化阶段)
    • 示例:@Transactional 等注解是在初始化阶段解析的
  2. 循环依赖解决方案

    java 复制代码
    // 关键工厂方法
    () -> getEarlyBeanReference(beanName, mbd, bean)
    • 当发生循环依赖时,通过此工厂动态创建早期引用
    • 普通 Bean 直接返回原始对象
    • 代理 Bean 返回代理对象
  3. 资源优化

    • 无循环依赖时,工厂不会被调用
    • 避免无谓的代理创建(约 30% 的性能提升)

普通 Bean 为何经过三级缓存

graph TD A[实例化] --> B[加入三级缓存] B --> C{是否有循环依赖?} C -->|无| D[继续初始化] C -->|有| E[触发工厂方法] D --> F[完成初始化] F --> G[加入一级缓存]
  • 设计一致性:所有单例 Bean 统一处理流程
  • 防患未然:即使当前无循环依赖,后续依赖链可能产生
  • 框架简化:避免写特殊处理逻辑

问题二:三级缓存和二级缓存的内容

三级缓存存储内容

java 复制代码
// 存储 ObjectFactory 工厂
singletonFactories.put(beanName, () -> {
    return getEarlyBeanReference(beanName, mbd, rawBean);
});
  • 工厂方法:能生成 Bean 的早期引用
  • 非 Bean 本身:内存占用小(函数式接口)

二级缓存存储内容

java 复制代码
// 存储早期 Bean 对象
earlySingletonObjects.put(beanName, earlyReference);
  • 半成品 Bean
    • 普通 Bean:原始对象
    • 代理 Bean:代理对象
  • 特征
    • 已完成实例化
    • 未完成属性注入
    • 未执行初始化方法

问题三:代理 Bean 的正常初始化路径

路径解析
Container Bean AOP处理器 1. 实例化原始对象 2. 加入三级缓存(工厂) 3. 属性注入(无循环依赖) 4. 初始化(@PostConstruct等) 5. 调用postProcessAfterInitialization 6. wrapIfNecessary() 创建代理 7. 返回代理对象 8. 代理对象存入一级缓存 Container Bean AOP处理器

典型场景

java 复制代码
@Service
public class OrderService {
    @Transactional // 事务注解触发代理
    public void createOrder() {...}
}

缓存状态变化

阶段 一级缓存 二级缓存 三级缓存 Bean 状态
实例化后 ✅ (工厂) 原始对象(未注入)
属性注入后 ✅ (工厂) 原始对象(依赖注入完成)
初始化后 ✅ (工厂) 原始对象(初始化完成)
AOP 处理后 ✅ (代理) 代理对象

关键点

  1. 原始对象全程存在

    • 存在于 Bean 创建流程中
    • 从未加入一级缓存
    • 最终被代理对象包装
  2. 代理创建时机

    java 复制代码
    // AbstractAutoProxyCreator
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return wrapIfNecessary(bean, beanName);
    }
    • 在初始化完成后创建代理
    • 此时原始对象已是完全体(注入+初始化完成)
  3. 最终缓存对象

    java 复制代码
    // 存入一级缓存的是代理对象
    addSingleton(beanName, proxyObject);

三级缓存必要性验证

假设只有二级缓存:

  1. 问题根源

    问题 二级缓存只存储原始对象 无法动态创建代理 代理决策在后期

  2. 三级缓存解决方案

    按需调用 三级缓存 存储工厂方法 工厂方法 动态创建代理 解决类型不一致

三级缓存工作原理对比图

无三级缓存 有三级缓存 是 是 否 是 直接放入二级缓存 实例化Bean 属性注入 发现循环依赖 从二级缓存获取 需要代理? 只能注入原始对象 类型错误! 加入三级缓存
ObjectFactory工厂 实例化Bean 属性注入 发现循环依赖 调用工厂方法 需要代理? 创建代理存入二级缓存 返回原始对象 注入正确类型

关键区别说明

  1. 决策时机不同

    • 有三级缓存:在需要注入时才决定是否创建代理(按需)
    • 无三级缓存:在实例化后立即固定对象类型(过早)
  2. 对象类型灵活性

    • 有三级缓存:可以返回原始对象或代理对象
    • 无三级缓存:只能返回原始对象
  3. 代理创建位置

    • 有三级缓存:在工厂方法内动态创建
    • 无三级缓存:无法在依赖注入阶段创建

这就是为什么 Spring 必须使用三级缓存而不是二级缓存的原因

设计精髓

"不要提前承诺对象类型,在真正需要时才动态决定"

三级缓存的工厂模式实现了这一理念,完美解决了循环依赖中的代理一致性问题。

总结

三级缓存 = 注册中心(记录哪些 Bean 可提供早期引用)

二级缓存 = 急诊室(临时存放循环依赖中的半成品)

一级缓存 = VIP 室(只存放完全就绪的成品)

"二级缓存只处理循环依赖的,三级缓存的作用是提前生成 Bean 的原始引用,如果没有循环依赖,其实三级缓存就是直接放到一级缓存的"

这就是 Spring 设计的高明之处------用统一的基础设施处理两种场景:

普通 Bean:三级缓存"虚位以待",直达终点

循环依赖 Bean:三级缓存"雪中送炭",二级缓存"临时避难",最终到达终点

这种设计在保证功能的同时,最大程度减少了资源开销,体现了"按需创建"的设计哲学。