目录
一、先明确前提
二、三级缓存定义(源码三个 Map)
1. 一级缓存:singletonObjects
2. 二级缓存:earlySingletonObjects
3. 三级缓存:singletonFactories
三、完整获取 Bean 流程(getSingleton 核心逻辑)
整体执行步骤
四、结合「循环依赖」实战走一遍(最经典场景)
场景
分步流程
步骤 1:开始创建 Bean A
步骤 2:开始创建 Bean B
步骤 3:再次获取 Bean A(循环依赖触发)
步骤 4:B 完成属性填充 & 初始化
步骤 5:A 完成创建
最终状态
五、核心问题解答(面试高频)
1. 为什么需要三级缓存?只用一级 + 二级不行吗?
2. 为什么构造器注入的循环依赖无法解决?
3. 二级缓存的作用是什么?
4. 原型 Bean 为什么没有循环依赖问题?
5. 三级缓存各自什么时候存、什么时候删?
六、精简总结(背诵版)
Spring单例 Bean 三级缓存核心目的:解决单例 Bean 循环依赖,只针对singleton、非构造器注入的场景生效。
一、先明确前提
- 只作用于单例 Bean(singleton),原型 Bean 不缓存、不存在循环依赖解决方案。
- 构造器注入循环依赖无法被三级缓存解决,会直接抛异常; 仅支持:setter 注入 / 字段注入(@Autowired)的循环依赖。
- 三级缓存都存在于
DefaultSingletonBeanRegistry类中。
二、三级缓存定义(源码三个 Map)
1. 一级缓存:singletonObjects
// 完整、初始化完成的单例 Bean(成品) private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);- 作用:存放完全创建好、属性填充、初始化全部完成的单例 Bean。
- 业务正常使用时,优先从这里拿 Bean。
- 特征:成品 Bean,对外提供服务。
2. 二级缓存:earlySingletonObjects
// 提前曝光的半成品 Bean(已实例化、未完成属性填充+初始化) private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);- 作用:存放已经实例化(new 出来),但还没完成属性注入、初始化的半成品 Bean。
- 专门用来提前暴露实例,阻断循环依赖。
- 特征:裸对象,还没走完完整 Bean 生命周期。
3. 三级缓存:singletonFactories
// Bean 工厂对象(ObjectFactory),用来生成半成品 Bean private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);- 存放的不是 Bean 本身,而是「Bean 工厂」(
ObjectFactory函数式接口)。 - 核心价值:支持 AOP 动态代理。 如果 Bean 需要被 AOP 代理,
ObjectFactory.getObject()会返回代理对象,而非原实例。 - 只有刚实例化、还没放入二级缓存时,才会存在于此。
总结三级角色: 1 级:成品 Bean2 级:普通半成品 Bean3 级:生成半成品 / 代理 Bean 的工厂
三、完整获取 Bean 流程(getSingleton 核心逻辑)
Spring 获取单例 Bean 严格遵循1 级 → 2 级 → 3 级顺序查找,源码入口:DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference)
整体执行步骤
先查一级缓存
singletonObjects- 存在:直接返回完整 Bean,流程结束。
- 不存在:进入下一步。
判断当前 Bean 是否正在创建中Spring 用
singletonsCurrentlyInCreation集合标记「正在创建的 Bean」。- 不在创建中:说明首次创建,继续走实例化流程。
- 正在创建中:说明出现循环依赖,进入二级、三级缓存查找。
查询二级缓存
earlySingletonObjects- 存在:直接返回半成品 Bean,解决循环依赖。
- 不存在:进入三级缓存。
查询三级缓存
singletonFactories- 取出
ObjectFactory工厂对象,调用getObject()生成半成品 Bean(可能是代理对象)。 - 将生成的 Bean存入二级缓存。
- 从三级缓存中删除当前 BeanName(三级工厂只临时用一次)。
- 返回该半成品 Bean。
- 取出
Bean 完整创建完毕后 最终把完整 Bean 放入一级缓存,同时清空二、三级缓存中对应数据。
四、结合「循环依赖」实战走一遍(最经典场景)
场景
- Bean A 依赖 Bean B
- Bean B 依赖 Bean A
- 均为singleton + @Autowired 字段注入(可被三级缓存解决)
分步流程
步骤 1:开始创建 Bean A
- 查一级缓存:无。
- 标记
A为正在创建(加入singletonsCurrentlyInCreation)。 - 实例化 A(
new A())→半成品 A 诞生。 - 把 A 的 ObjectFactory 放入三级缓存
singletonFactories(支持 AOP 代理)。 - 开始属性填充:发现 A 依赖 B,触发
getBean(B)。
步骤 2:开始创建 Bean B
- 查一级缓存:无。
- 标记
B为正在创建。 - 实例化 B → 半成品 B。
- 把 B 的 ObjectFactory 放入三级缓存。
- 属性填充:B 依赖 A,触发
getBean(A)。
步骤 3:再次获取 Bean A(循环依赖触发)
- 查一级缓存:无。
- 发现A 正在创建中→ 判定循环依赖。
- 查二级缓存:无。
- 查三级缓存:拿到 A 的
ObjectFactory。 - 执行
getObject()得到半成品 A(可能是代理对象)。 - 将半成品 A 存入二级缓存,并删除三级缓存中的 A。
- 返回半成品 A 给 B。
步骤 4:B 完成属性填充 & 初始化
B 拿到 A 后,完成所有属性注入、初始化、后置处理。完整 B 存入一级缓存,二、三级清理 B 相关数据。 B 创建完成,回到 A 的属性填充流程。
步骤 5:A 完成创建
A 成功拿到完整 B,完成属性填充、初始化。完整 A 存入一级缓存,清理二、三级缓存中 A。
最终状态
- 一级缓存:A、B 两个完整单例 Bean
- 二级、三级缓存:无数据
循环依赖完美解决。
五、核心问题解答(面试高频)
1. 为什么需要三级缓存?只用一级 + 二级不行吗?
不行,核心原因:AOP 动态代理
- 如果只有一、二级缓存: Bean 实例化后直接丢进二级缓存,此时原始对象固定。 若该 Bean 需要 AOP 代理,循环依赖时注入的是原对象,不是代理对象,业务出错。
- 三级缓存存的是
ObjectFactory: 调用getObject()时,会执行SmartInstantiationAwareBeanPostProcessor,动态生成代理对象。 保证循环依赖中,注入的也是代理 Bean。
一句话:三级缓存是为了兼容 AOP 代理。
2. 为什么构造器注入的循环依赖无法解决?
构造器注入是实例化阶段就需要依赖:
- 创建 A → 执行构造方法,直接需要 B。
- 创建 B → 执行构造方法,直接需要 A。
- 连实例都 new 不出来,根本走不到「放入三级缓存」那一步。 Spring 提前实例化半成品的逻辑失效,直接抛出
BeanCurrentlyInCreationException。
3. 二级缓存的作用是什么?
- 三级工厂只使用一次,生成对象后立刻移入二级缓存。
- 若存在多条依赖链同时获取该半成品 Bean,直接从二级取,避免重复执行 ObjectFactory 生成对象,提升效率。
4. 原型 Bean 为什么没有循环依赖问题?
原型 Bean 每次getBean都会新建对象,不做任何缓存,自然不存在循环引用卡死。
5. 三级缓存各自什么时候存、什么时候删?
- 三级缓存:Bean 刚实例化后存入;生成对象移入二级后立即删除。
- 二级缓存:循环依赖时临时存放半成品;Bean 完整创建后删除。
- 一级缓存:Bean 完全初始化完毕后存入;容器销毁时才清除。
六、精简总结(背诵版)
三级缓存结构
- 1 级
singletonObjects:完整成品单例 Bean - 2 级
earlySingletonObjects:提前曝光的半成品 Bean - 3 级
singletonFactories:生成 Bean / 代理的 ObjectFactory 工厂
- 1 级
核心作用整体解决单例 Bean setter / 字段注入 的循环依赖;三级缓存专门适配AOP 代理。
查找顺序一级 → 二级 → 三级,循环依赖触发二、三级使用。
限制构造器注入循环依赖、原型 Bean 不受这套机制管辖。