Spring 三级缓存详解
Spring 框架中的 三级缓存 主要与 Spring IoC 容器(特别是 ApplicationContext
)的 Bean 实例化过程 相关。它的作用是优化 Bean 的创建和初始化,尤其是在 单例 Bean 的创建过程中,避免多次创建 Bean 实例,确保应用程序的性能和资源管理。
Spring Bean 的生命周期
在 Spring 中,Bean 的生命周期包括以下几个关键阶段:
- 实例化:创建 Bean 实例。
- 依赖注入:将 Bean 的依赖项注入到实例中。
- 初始化:执行初始化回调方法(如
@PostConstruct
或自定义初始化方法)。 - 销毁:销毁 Bean 实例。
对于 单例 Bean,Spring 使用了三级缓存来保证在多线程环境中 懒加载 和 线程安全。
Spring 三级缓存的作用
Spring 三级缓存的目的是优化单例 Bean 的创建过程,避免 Bean 被多次实例化。尤其是在 懒加载 或 依赖注入过程中,Spring 必须保证:
- 线程安全:防止多个线程同时创建同一个 Bean 实例。
- 性能优化:通过缓存避免重复创建 Bean 实例。
Spring 三级缓存的结构
Spring 的三级缓存主要由以下三个缓存组成:
singletonObjects
:存放已经创建并且完全初始化的单例 Bean。earlySingletonObjects
:存放早期暴露的单例 Bean,这些 Bean 尚未完全初始化,但已经实例化。这个缓存是用于 循环依赖 的解决。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 将无法处理这种情况。
- 实例化 Bean A:Spring 首先尝试实例化 Bean A。
- 依赖注入 B:在 Bean A 的依赖注入阶段,Spring 发现 A 依赖 B,于是继续实例化 Bean B。
- 实例化 Bean B:Spring 实例化 Bean B,然后发现 B 也依赖 A,这时 Spring 会通过 三级缓存 暴露一个早期实例。
- 早期暴露 Bean A:由于 Bean A 还未完全初始化,Spring 会将其早期实例暴露到
earlySingletonObjects
缓存中。 - 使用早期实例:通过
earlySingletonObjects
缓存,Bean B 可以继续依赖注入 Bean A 的早期实例,避免了死锁。 - 完成初始化:一旦 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 三级缓存的详细流程
- 创建 Bean 实例:Spring 在容器初始化时,会首先检查是否存在对应的单例 Bean。如果存在,它会从
singletonObjects
中直接获取。如果不存在,则进入下一步。 - 放入
singletonFactories
缓存:如果 Bean 需要被懒加载,或者是通过工厂方法创建的实例,Spring 会将工厂方法添加到singletonFactories
缓存中。 - 放入
earlySingletonObjects
缓存:当 Spring 实例化 Bean 的过程中,如果遇到循环依赖,Spring 会将部分尚未初始化的 Bean 暴露在earlySingletonObjects
缓存中。 - 最终放入
singletonObjects
:当所有的依赖注入和初始化完成后,Bean 最终会被添加到singletonObjects
缓存中,这时这个 Bean 实例就可以被其他 Bean 或者外部调用引用了。
Spring 三级缓存与多线程环境
在多线程环境下,Spring 的三级缓存能够保证:
- 线程安全:Spring 确保即使多个线程同时请求同一个单例 Bean,只有第一个线程会执行实例化过程,其他线程会共享已经实例化的 Bean 实例。
- 性能优化:通过三级缓存的机制,避免了不必要的 Bean 实例化,减少了性能开销。
总结
Spring 的三级缓存是 Spring 容器中用于管理 单例 Bean 实例的关键机制。它的核心目的是解决 循环依赖 和 懒加载 的问题,提高容器的性能,并保证多线程环境中的线程安全。
singletonObjects
:存储已经完全初始化的 Bean。earlySingletonObjects
:存储早期暴露的单例 Bean(主要解决循环依赖)。singletonFactories
:存储生成单例 Bean 的工厂方法,支持懒加载。
三级缓存机制使得 Spring 能够在保持高性能的同时,处理复杂的依赖关系,保证应用程序的稳定性和高效性。
发表回复