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

发布于:2025-08-15 ⋅ 阅读:(17) ⋅ 点赞:(0)

bean的流程

三级缓存‌存储:

存储的是工厂对象(ObjectFactory),用于生成代理对象或普通 Bean 的早期引用。
工厂的 getObject() 方法会调用后置处理器生成代理对象,或直接返回普通 Bean 的早期引用。
‌二级缓存‌存储:
存储的是早期引用,可能是未完成初始化的普通 Bean 或代理对象的早期引用。
二级缓存仅在有循环依赖时参与。
‌一级缓存‌存储:
存储的是完全初始化好的 Bean,包括代理对象和普通 Bean。
‌用户创建的 Bean 的路径‌。

bean依赖创建:

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

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

Bean 实例化后,工厂对象放入三级缓存(但无循环依赖时不会实际使用)。
初始化完成后,直接将完全初始化的 Bean 放入一级缓存。
‌路径‌:三级缓存(工厂)→ 一级缓存(但三级缓存的工厂未实际使用)。

有代理的Bean‌:

‌路径 1‌(‌有循环依赖有代理):三级缓存工厂生成早期引用 → 二级缓存 → 一级缓存。 --这里生成的也有可能
‌路径 2‌ (无循环依赖有代理):正常初始化(无循环依赖)时,通过后置处理器生成代理对象 → 一级缓存。


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

三级缓存的核心作用

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

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

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

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

普通 Bean 为何经过三级缓存

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

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

三级缓存存储内容

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

二级缓存存储内容

// 存储早期 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处理器

典型场景

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

缓存状态变化

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

关键点

  1. 原始对象全程存在

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

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

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

三级缓存必要性验证

假设只有二级缓存:

  1. 问题根源

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

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

三级缓存工作原理对比图

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

关键区别说明

  1. 决策时机不同

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

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

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

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

设计精髓

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

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

总结

三级缓存 = 注册中心(记录哪些 Bean 可提供早期引用)
二级缓存 = 急诊室(临时存放循环依赖中的半成品)
一级缓存 = VIP 室(只存放完全就绪的成品)

“二级缓存只处理循环依赖的,三级缓存的作用是提前生成 Bean 的原始引用,如果没有循环依赖,其实三级缓存就是直接放到一级缓存的”
这就是 Spring 设计的高明之处——用统一的基础设施处理两种场景:
普通 Bean:三级缓存"虚位以待",直达终点
循环依赖 Bean:三级缓存"雪中送炭",二级缓存"临时避难",最终到达终点
这种设计在保证功能的同时,最大程度减少了资源开销,体现了"按需创建"的设计哲学。


网站公告

今日签到

点亮在社区的每一天
去签到