Spring 三级缓存详解

Spring 框架中的 三级缓存 主要与 Spring IoC 容器(特别是 ApplicationContext)的 Bean 实例化过程 相关。它的作用是优化 Bean 的创建和初始化,尤其是在 单例 Bean 的创建过程中,避免多次创建 Bean 实例,确保应用程序的性能和资源管理。

Spring Bean 的生命周期

在 Spring 中,Bean 的生命周期包括以下几个关键阶段:

  1. 实例化:创建 Bean 实例。
  2. 依赖注入:将 Bean 的依赖项注入到实例中。
  3. 初始化:执行初始化回调方法(如 @PostConstruct 或自定义初始化方法)。
  4. 销毁:销毁 Bean 实例。

对于 单例 Bean,Spring 使用了三级缓存来保证在多线程环境中 懒加载 和 线程安全

Spring 三级缓存的作用

Spring 三级缓存的目的是优化单例 Bean 的创建过程,避免 Bean 被多次实例化。尤其是在 懒加载 或 依赖注入过程中,Spring 必须保证:

  • 线程安全:防止多个线程同时创建同一个 Bean 实例。
  • 性能优化:通过缓存避免重复创建 Bean 实例。

Spring 三级缓存的结构

Spring 的三级缓存主要由以下三个缓存组成:

  1. singletonObjects:存放已经创建并且完全初始化的单例 Bean。
  2. earlySingletonObjects:存放早期暴露的单例 Bean,这些 Bean 尚未完全初始化,但已经实例化。这个缓存是用于 循环依赖 的解决。
  3. singletonFactories:存放创建单例对象的工厂方法(ObjectFactory),用于懒加载和依赖注入。

详细解析

1. singletonObjects 缓存

singletonObjects 缓存用于存放已经创建并且完全初始化的单例 Bean。当 Spring 完成了 Bean 的实例化、依赖注入和初始化后,最终的 Bean 实例将存储在 singletonObjects 中。

  • 作用:用于保存已经创建好的 Bean 实例,避免重复创建。
  • 更新时机:当一个单例 Bean 完成实例化和初始化后,会将该 Bean 添加到 singletonObjects 中。
Map<String, Object> singletonObjects = new HashMap<>();

2. earlySingletonObjects 缓存

earlySingletonObjects 缓存用于存放早期暴露的单例 Bean。当 Spring 在创建单例 Bean 的过程中遇到依赖注入时,它会尝试通过提前暴露部分 Bean 的实例来解决 循环依赖 问题。具体来说,Spring 会在 Bean 实例化阶段(但还未完成初始化)将实例暴露到 earlySingletonObjects 缓存中,供其他 Bean 使用。

  • 作用:解决 循环依赖 问题,早期暴露 Bean 实例。
  • 更新时机:在单例 Bean 实例化过程中,Bean 的 FactoryBean 或 ObjectFactory 会通过 earlySingletonObjects 暴露尚未完成初始化的 Bean 实例。
Map<String, Object> earlySingletonObjects = new HashMap<>();

3. singletonFactories 缓存

singletonFactories 缓存用于存放生成单例 Bean 的工厂方法(ObjectFactory)。这个缓存是在 Spring 容器创建 Bean 实例时,如果需要懒加载或依赖注入时,Spring 会将相应的工厂方法放到 singletonFactories 中。

  • 作用:用于懒加载和依赖注入。
  • 更新时机:当单例 Bean 的实例化过程还没有完成时,Spring 会将生成该 Bean 的工厂方法保存在 singletonFactories 中。等到需要这个 Bean 时,再调用工厂方法创建实例。
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

Spring 三级缓存如何解决循环依赖

Spring 通过三级缓存来解决 循环依赖(即两个或多个 Bean 相互依赖),这种循环依赖问题通常出现在 构造器注入 中,但 Spring 的三级缓存主要用于 setter 注入 的循环依赖解决。

循环依赖解决过程

假设有两个 Bean A 和 B,它们彼此依赖(即 A 依赖 B,B 依赖 A),在没有三级缓存的情况下,Spring 将无法处理这种情况。

  1. 实例化 Bean A:Spring 首先尝试实例化 Bean A。
  2. 依赖注入 B:在 Bean A 的依赖注入阶段,Spring 发现 A 依赖 B,于是继续实例化 Bean B。
  3. 实例化 Bean B:Spring 实例化 Bean B,然后发现 B 也依赖 A,这时 Spring 会通过 三级缓存 暴露一个早期实例。
  4. 早期暴露 Bean A:由于 Bean A 还未完全初始化,Spring 会将其早期实例暴露到 earlySingletonObjects缓存中。
  5. 使用早期实例:通过 earlySingletonObjects 缓存,Bean B 可以继续依赖注入 Bean A 的早期实例,避免了死锁。
  6. 完成初始化:一旦 Bean A 和 Bean B 都完成初始化,它们会被放入 singletonObjects 缓存中。

具体代码分析

// 创建单例 Bean A
singletonFactories.put(beanName, () -> createBean(beanName));

// 在创建过程中,Bean A 暴露为早期 Bean
earlySingletonObjects.put(beanName, beanInstance);

// 待 Bean A 初始化完成后,添加到正式的 singletonObjects 中
singletonObjects.put(beanName, beanInstance);

Spring 三级缓存的详细流程

  1. 创建 Bean 实例:Spring 在容器初始化时,会首先检查是否存在对应的单例 Bean。如果存在,它会从 singletonObjects 中直接获取。如果不存在,则进入下一步。
  2. 放入 singletonFactories 缓存:如果 Bean 需要被懒加载,或者是通过工厂方法创建的实例,Spring 会将工厂方法添加到 singletonFactories 缓存中。
  3. 放入 earlySingletonObjects 缓存:当 Spring 实例化 Bean 的过程中,如果遇到循环依赖,Spring 会将部分尚未初始化的 Bean 暴露在 earlySingletonObjects 缓存中。
  4. 最终放入 singletonObjects:当所有的依赖注入和初始化完成后,Bean 最终会被添加到 singletonObjects 缓存中,这时这个 Bean 实例就可以被其他 Bean 或者外部调用引用了。

Spring 三级缓存与多线程环境

在多线程环境下,Spring 的三级缓存能够保证:

  • 线程安全:Spring 确保即使多个线程同时请求同一个单例 Bean,只有第一个线程会执行实例化过程,其他线程会共享已经实例化的 Bean 实例。
  • 性能优化:通过三级缓存的机制,避免了不必要的 Bean 实例化,减少了性能开销。

总结

Spring 的三级缓存是 Spring 容器中用于管理 单例 Bean 实例的关键机制。它的核心目的是解决 循环依赖 和 懒加载 的问题,提高容器的性能,并保证多线程环境中的线程安全。

  • singletonObjects:存储已经完全初始化的 Bean。
  • earlySingletonObjects:存储早期暴露的单例 Bean(主要解决循环依赖)。
  • singletonFactories:存储生成单例 Bean 的工厂方法,支持懒加载。

三级缓存机制使得 Spring 能够在保持高性能的同时,处理复杂的依赖关系,保证应用程序的稳定性和高效性。