Spring 的三级缓存机制

Spring 在处理 循环依赖 时使用了 三级缓存机制,这是 Singleton Bean 创建过程中为了解决循环依赖的一种策略。通过三级缓存,Spring 可以提前暴露未完全初始化的对象,避免循环依赖导致的无限递归错误。


1. 三级缓存的结构

Spring IOC 容器的 三级缓存分为以下三个部分:

  1. 一级缓存(singletonObjects)

    • 存储的是完全初始化完成的单例对象
    • 类型:ConcurrentHashMap<String, Object>
    • 用于直接获取初始化完成的 bean。
  2. 二级缓存(earlySingletonObjects)

    • 存储的是早期暴露的单例对象(已实例化但尚未完全初始化的 bean)。
    • 类型:ConcurrentHashMap<String, Object>
    • 用于解决部分场景下的循环依赖问题,避免不必要的代理创建。
  3. 三级缓存(singletonFactories)

    • 存储的是ObjectFactory(对象工厂),用于生成 bean 的代理或延迟引用。
    • 类型:ConcurrentHashMap<String, ObjectFactory<?>>
    • 用于在需要时生成早期对象的引用,并放入二级缓存。

2. 三级缓存的工作流程

当遇到循环依赖时,Spring 会按照以下步骤来创建和管理 bean:

  1. 检查一级缓存:

    • singletonObjects 中查找,如果找到,说明这个 bean 已完全初始化,直接返回。
  2. 检查二级缓存:

    • 如果一级缓存中没有,则从 earlySingletonObjects(二级缓存)中查找。
    • 如果找到,说明这个 bean 还没完全初始化,但可以提供引用,直接返回。
  3. 检查三级缓存:

    • 如果一级和二级缓存都没有,从 singletonFactories(三级缓存)中获取一个对象工厂,调用工厂方法创建早期引用,并放入二级缓存。
  4. 放入一级缓存:

    • 完成 bean 的初始化后,将最终的对象存入 singletonObjects(一级缓存),并从二级、三级缓存中移除。

3. 示例:如何解决循环依赖

假设存在以下两个类:AB,它们互相依赖。

class A {
    
    
    private B b;

    public A(B b) {
    
    
        this.b = b;
    }
}

class B {
    
    
    private A a;

    public void setA(A a) {
    
    
        this.a = a;
    }
}

Spring 容器中创建这两个对象时,会出现循环依赖:

  1. Spring 尝试实例化 A,但 A 的构造器依赖 B
  2. 容器开始创建 B,但 BsetA() 方法又依赖 A
  3. 为了解决这个循环依赖问题,Spring 将未完全初始化的 A 的引用提前暴露到二级缓存中。
  4. 在创建 B 时,B 可以通过二级缓存获取 A 的引用,完成注入。
  5. 最终,AB 都完成初始化,并分别存入一级缓存。

4. 代码中的核心逻辑

Spring 的核心实现位于 DefaultSingletonBeanRegistry 类中。

  • 三级缓存的使用逻辑大致如下:
// 从一级缓存中获取 bean
Object singletonObject = singletonObjects.get(beanName);
if (singletonObject == null) {
    
    
    // 如果一级缓存没有,则尝试从二级缓存获取
    singletonObject = earlySingletonObjects.get(beanName);
    if (singletonObject == null) {
    
    
        // 如果二级缓存也没有,则从三级缓存的 ObjectFactory 中获取
        ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
        if (singletonFactory != null) {
    
    
            singletonObject = singletonFactory.getObject();
            // 提前暴露到二级缓存中
            earlySingletonObjects.put(beanName, singletonObject);
            singletonFactories.remove(beanName);
        }
    }
}
return singletonObject;

5. 为什么需要三级缓存?

  • 三级缓存的作用: 提前暴露一个未完全初始化的对象(或其代理)引用,避免循环依赖导致的死锁问题。
  • 为什么不直接使用二级缓存?
    • 二级缓存只能存储已经创建出的对象引用,但某些代理对象需要延迟创建。三级缓存通过 对象工厂(ObjectFactory) 延迟创建这些代理,灵活性更高。

6. 适用场景与限制

  1. 适用于 Setter 注入:

    • Spring 通过三级缓存可以处理 Setter 注入 场景中的循环依赖。
  2. 不适用于构造注入:

    • 如果两个 bean 通过构造器相互依赖,Spring 无法通过三级缓存解决,因为构造器必须一次性注入所有依赖。

7. 总结

  • 一级缓存: 存储完全初始化的对象。
  • 二级缓存: 存储早期暴露的对象引用,用于解决循环依赖。
  • 三级缓存: 存储对象工厂,用于延迟创建代理对象,并转移到二级缓存中。

三级缓存机制帮助 Spring 处理 Setter 注入 引发的循环依赖问题,但对于 构造器注入 的循环依赖无能为力。

猜你喜欢

转载自blog.csdn.net/heromps/article/details/143116951