大家好,我是程序员强子。
最近在后台收到小伙伴留言,说想让我分析一下 循环依赖到底是怎么回事,缓存又为啥设计成三级,两级不行嘛~ 等等这些问题。
那今天我们就盘一下这些问题,争取一次性拿下~
来看下今天的知识点:
- 循环依赖的Bean创建流程 :涉及的核心类 、具体创建流程
- 三级缓存的设计思想:选择三级缓存而非两级的具体原因、三级缓存的核心设计思路、以及每一级缓存各自承担的核心作用
来不及解释了,发车了~
Bean创建流程
以BeanA(单例,Setter 注入 BeanB) 和BeanB(单例,Setter 注入 BeanA) 的循环依赖为例。
前置条件
- BeanA 和 BeanB 均为单例(Spring 默认作用域);
- 依赖注入方式为Setter 注入 ;
- 容器底层由DefaultListableBeanFactory实现,三级缓存逻辑由DefaultSingletonBeanRegistry维护
那会不会跟强子一样有疑问:
DefaultListableBeanFactory 和 **DefaultSingletonBeanRegistry **到底是什么?有什么作用呢?
核心类总结
DefaultListableBeanFactory 是什么?
首先 回顾一下 BeanFactory ,是 IoC 容器的最顶层核心接口,定义了 IoC 容器的基础规范
-
仅提供最基础的 Bean 操作能力
- getBean()(获取 Bean 实例)
- containsBean()(判断 Bean 是否存在)
- isSingleton()(判断 Bean 是否为单例)
-
是 最小功能集,后续所有实现都是围绕这接口扩展
DefaultListableBeanFactory是 BeanFactory 体系中最完整的实现类,是容器的 基础骨架
- 实现了BeanFactory 接口
- 实现了ListableBeanFactory(支持批量获取Bean)
- 实现了AutowireCapableBeanFactory(支持自动装配)
- 实现了BeanDefinitionRegistry(支持注册Bean 定义)
- 具备 Bean 定义注册,实例化,依赖注入,自动装配 能力
我们 比较熟悉的 ApplicationContext,底层依赖 DefaultListableBeanFactory
- ApplicationContext 的具体实现 ClassPathXmlApplicationContext、AnnotationConfigApplicationContext
- 内部都会创建 DefaultListableBeanFactory 实例 作为 bean服务接口~
- 所有 Bean 的创建、管理、缓存 操作最终都委托给 DefaultListableBeanFactory 执行
所以 得出一个结论: DefaultListableBeanFactory 全权管理 注册Bean、处理循环依赖、管理三级缓存 !
那DefaultSingletonBeanRegistry 是什么?
是 Spring 中单例 Bean 缓存机制的核心实现类
public class DefaultSingletonBeanRegistry
extends SimpleAliasRegistry
implements SingletonBeanRegistry {
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** Set of registered singletons, containing the bean names in registration order. */
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/** Names of beans currently excluded from in creation checks. */
private final Set<String> inCreationCheckExclusions =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
//省略其他代码
...
}
-
定义三级缓存的存储结构
- singletonObjects(一级缓存):存储完全初始化完成的单例 Bean,是最终可直接使用的 Bean 缓存;
- earlySingletonObjects(二级缓存):存储早期暴露的 Bean 实例(已实例化但未完成属性填充和初始化的 Bean),用于解决循环依赖时的临时引用;
- singletonFactories(三级缓存):存储Bean 的对象工厂(ObjectFactory),用于在需要时创建早期 Bean 实例(AOP 代理对象或原始对象)
-
提供缓存操作核心方法
- 单例 Bean 的获取(getSingleton())
- 注册(addSingleton())
- 早期对象获取(getEarlyBeanReference())
Spring 框架的类设计严格遵循单一职责原则 ,将不同功能解耦到不同类中
- DefaultSingletonBeanRegistry 的核心职责:专注于单例 Bean 的缓存管理、生命周期跟踪与循环依赖处理
- DefaultListableBeanFactory 的核心职责:专注于 Bean 定义注册、加载,Bean实例化、依赖注入、自动装配等容器核心调度逻辑
这两个类的调用链路如下:
DefaultListableBeanFactory → AbstractBeanFactory(调用 getSingleton())→ DefaultSingletonBeanRegistry(执行三级缓存的查找 / 注册 / 升级)
好了,万事俱备只欠东风,跟着强子的脚步,进一步分析 具体创建流程~
具体创建流程
步骤 1:触发 BeanA 的创建
容器调用AbstractBeanFactory.getBean("beanA")
最终委托DefaultSingletonBeanRegistry.getSingleton处理:
- 检查一级缓存(singletonObjects):无 BeanA 实例;
- 检查二级缓存(earlySingletonObjects):无 BeanA 早期引用;
- 检查三级缓存(singletonFactories):无 BeanA 的工厂对象;
- 将 BeanA 标记为 创建中(加入singletonsCurrentlyInCreation集合,防止重复创建并检测循环依赖)
singletonsCurrentlyInCreation集合有什么作用?
-
最核心的作用:标记 Bean 的创建中状态,识别循环依赖
- 当 开始创建 单例 Bean 时,会先将 名称加入
- 当 Bean 完成初始化(实例化、属性填充、初始化方法执行等)后,再将其从集合中移除
-
防止单例 Bean 的重复实例化
- 多个线程同时尝试获取同一个未创建的Bean 时,第一个线程会将 Bean 加入集合并开始创建
- 后续线程检查到 Bean 已在集合中,会等待第一个线程创建完成,而非重新实例化
步骤 2:实例化 BeanA
AbstractAutowireCapableBeanFactory.createBeanInstance()
通过反射(Constructor.newInstance())创建 BeanA 的原始实例
仅完成对象实例化,属性为默认值
步骤 3:暴露 BeanA 的早期引用到三级缓存
将生成 BeanA 早期引用的ObjectFactory放入三级缓存(singletonFactories)
此时,ObjectFactory 是一个工厂接口 ,作用是延迟创建 / 获取对象
当调用 ObjectFactory.getObject() 时
- 需要代理 → 工厂返回代理对象
- 不需要代理 → 工厂返回原始对象
步骤 4:填充 BeanA 的属性
AbstractAutowireCapableBeanFactory.populateBean处理属性注入
发现 BeanA 依赖 BeanB,调用getBean("beanB")触发 BeanB 的创建流程
步骤 5:触发 BeanB 的创建
重复步骤 1-3
- 检查三级缓存,无 BeanB 相关记录,标记 BeanB 为 创建中
- 实例化 BeanB 的原始实例
- 将 BeanB 的ObjectFactory放入三级缓存
步骤 6:填充 BeanB 的属性
发现 BeanB 依赖 BeanA,调用getBean("beanA")获取 BeanA
步骤 7:获取 BeanA 的早期引用
再次调用getSingleton("beanA"):
- 检查一级缓存:无;
- 检查二级缓存:无;
- 检查三级缓存:存在 BeanA 的ObjectFactory;
- 调用ObjectFactory.getObject()生成 BeanA 的早期引用(若需代理则为代理对象,否则为原始对象);
- 将 BeanA 的早期引用从三级缓存移至二级缓存,并移除三级缓存中的 BeanA
- 返回 BeanA 的早期引用给 BeanB。
为什么执行了ObjectFactory.getObject()生成的产物是放到二级缓存而不是一级缓存?
二级缓存的存在是为了隔离早期对象与完整 Bean ,而一级缓存必须保证存储的是最终可用的单例Bean
而 ObjectFactory.getObject()生成的产物 未完成:属性填充与初始化 ,属于半成品,因此不能放到一级缓存中。
步骤 8:完成 BeanB 的初始化与注册
-
BeanB 拿到 BeanA 的早期引用,完成属性填充;
-
执行 BeanB 的初始化逻辑:
- 调用@PostConstruct注解方法;
- 执行InitializingBean.afterPropertiesSet();
- 调用自定义初始化方法(init-method);
-
注册 BeanB 到一级缓存,将 BeanB 完整实例放入singletonObjects;
-
移除 BeanB 在二级 / 三级缓存的记录,标记 BeanB 为 “创建完成”(从singletonsCurrentlyInCreation移除)。
步骤 9:完成 BeanA 的属性填充与初始化
-
回到 BeanA 的属性填充步骤,此时 BeanB 已存在于一级缓存,直接注入 BeanB 的完整实例;
-
执行 BeanA 的初始化逻辑(同 BeanB 的初始化步骤);
-
检查 BeanA 是否存在早期引用(二级缓存中)
- 若 BeanA 无需代理:直接使用原始实例;
- 若 BeanA 需代理:早期引用已为代理对象,直接复用;
-
注册 BeanA 到一级缓存;
-
移除 BeanA 在二级缓存的记录,标记 BeanA 为 创建完成(从singletonsCurrentlyInCreation移除)
大家会不会杠一下,为什么一定要三级缓存呢?二级行不行?
那就跟强子来深入分析一下为啥一定要三级,少一级都不行的原因~
为什么一定要三级缓存?
第一步:最初的需求
最初的需求,保证单例 Bean 全局唯一,且仅对外暴露 完全初始化完成 的实例(实例化 + 属性填充 + 初始化 + AOP 代理)
此时最朴素的设计是用一个 Map 存储:
// 一级缓存:存储最终可用的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
- 单例 Bean 的核心价值是 复用,一个 Map 完全满足 存和 取 的基础需求;
- 只有当 Bean 完全初始化后,才放入这个 Map,避免对外暴露不完整的 Bean
这个时候,问题出现了:
- 当出现循环依赖(如 A 依赖 B、B 依赖 A)时
- A 在初始化过程中需要 B,B 初始化过程中又需要 A
- 此时两个 Bean 都未进入singletonObjects,陷入 你等我、我等你 的循环中,死锁了~
第二步:解决基础循环依赖
设计者意识到:核心矛盾是 Bean未完全初始化,但依赖方需要它的引用。
既然无法提前完成初始化,能否在 Bean实例化后、初始化前,先把它的 早期引用 暴露出来?
于是新增第二个Map 存储早期引用
// 二级缓存:存储实例化完成但未初始化的早期Bean引用
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
- 对单例 Bean,实例化(调用构造器)后,此时 Bean 已经有了内存地址(引用),只是属性未填充、未初始化;
- 把这个早期引用存入earlySingletonObjects,当依赖方需要时,直接从这里取引用,打破循环依赖的死锁
新问题来了:如果 Bean 需要 AOP 代理 。。
早期引用是原始Bean实例,但最终需要的是代理实例
若依赖方B拿到的是原始实例,而 A 最终会变成代理实例,
就会导致 依赖不一致(B 依赖原始 A,容器里最终是代理 A)
第三步:处理代理Bean 的一致性
设计者进一步思考:不能直接存储早期实例,而应存储 “能生成最终实例或者代理实例的工厂
当依赖方需要时,通过工厂动态生成 Bean 的早期引用(需要代理则生成代理,不需要则返回原始)
保证依赖方拿到的引用与最终Bean一致
于是新增第三个Map 存储工厂:
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
- ObjectFactory是一个延迟执行的工厂接口,其getObject()方法会在真正需要引用时才调用;
- 当 Bean 实例化后,不直接存早期引用,而是存一个工厂:() -> getEarlyBeanReference(beanName, mbd, bean);
- 当依赖方需要该 Bean 时,调用工厂的getObject(),此时会触发AOP代理逻辑(由SmartInstantiationAwareBeanPostProcessor处理),生成代理后的早期引用;
- 生成后的早期引用会从三级缓存移到二级缓存(避免重复生成),保证全局唯一
安全校验
新增singletonsCurrentlyInCreation集合
在集合里面,就代表正在创建,标记为 正在创建的Bean
防止构造器注入循环依赖导致的死循环
三级缓存总结
- 一级缓存:守护 单例Bean最终一致性 的底线;
- 二级缓存:临时存储 可用的早期引用,打破循环依赖;
- 三级缓存:解决 代理Bean早期引用的一致性 问题,兼顾延迟代理的设计原则
Spring无法解决的循环依赖的场景
构造器注入导致的循环依赖
代码demo
@Component
public class BeanA {
private BeanB beanB;
// 构造器注入BeanB
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB {
private BeanA beanA;
// 构造器注入BeanA
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
问题解析
- Spring 启动时会尝试创建 BeanA,调用BeanA(BeanB)构造器时需要先获取 BeanB;
- 创建 BeanB 时,调用BeanB(BeanA)构造器又需要先获取 BeanA。
- 此时两个 Bean 都处于 构造器实例化阶段,还未执行到 实例化后注册三级缓存 的步骤
- 而三级缓存是实例化后才注册 ObjectFactory
- 因此 无法暴露早期引用,
- 最终触发BeanCurrentlyInCreationException异常,循环依赖无法解决
解决方案
方案1 :改为 Setter 注入
@Component
public class BeanA {
private BeanB beanB;
// Setter注入替代构造器注入
@Autowired
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB {
private BeanA beanA;
@Autowired
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
Setter 注入允许 Bean先实例化(触发三级缓存注册),再填充依赖,符合 Spring 循环依赖解决方案的流程
方案 2:构造器注入 + @Lazy 延迟加载
@Component
public class BeanA {
private BeanB beanB;
// 对构造器依赖加@Lazy,生成代理对象延迟初始化
public BeanA(@Lazy BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB {
private BeanA beanA;
public BeanB(@Lazy BeanA beanA) {
this.beanA = beanA;
}
}
原理:@Lazy 在构造器注入时生成依赖 Bean 的代理对象,避免实例化阶段的循环等待。
方案3:使用 ObjectFactory 延迟获取依赖
@Component
public class BeanA {
private BeanB beanB;
public BeanA(ObjectFactory<BeanB> beanBFactory) {
// 延迟获取BeanB,避免构造器阶段直接依赖
this.beanB = beanBFactory.getObject();
}
}
@Component
public class BeanB {
private BeanA beanA;
public BeanB(ObjectFactory<BeanA> beanAFactory) {
this.beanA = beanAFactory.getObject();
}
}
ObjectFactory 封装依赖获取逻辑,延迟到实际使用时再触发依赖 Bean 的创建
总结
今天,终于把困扰很久的 循环依赖 和 三级缓存 问题 搞清楚了~
主要讲了 循环依赖的Bean创建流程 以及 三级缓存的设计思想,
还补充了 一些 核心类的总结,使得我们更好的了解 创建流程的内容~
感谢小伙伴给强子留言,也希望解 小伙伴的 疑问 ~
熟练度刷不停,知识点吃透稳,下期接着练~