写点什么

Java 王者修炼手册【Spring 篇 - 循环依赖 & 三级缓存】:Bean 创建流程 + 循环依赖 + 三级缓存原理 大揭秘

作者:DonaldCen
  • 2025-12-05
    广东
  • 本文字数:6101 字

    阅读完需:约 20 分钟

Java 王者修炼手册【Spring 篇 - 循环依赖 & 三级缓存】:Bean创建流程 + 循环依赖 +  三级缓存原理 大揭秘

大家好,我是程序员强子。

最近在后台收到小伙伴留言,说想让我分析一下 循环依赖到底是怎么回事,缓存又为啥设计成三级,两级不行嘛~ 等等这些问题。

那今天我们就盘一下这些问题,争取一次性拿下~

来看下今天的知识点:

  • 循环依赖的 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 的具体实现 ClassPathXmlApplicationContextAnnotationConfigApplicationContext

  • 内部都会创建 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 实例化依赖注入自动装配等容器核心调度逻辑

这两个类的调用链路如下:

DefaultListableBeanFactoryAbstractBeanFactory(调用 getSingleton())→ DefaultSingletonBeanRegistry(执行三级缓存的查找 / 注册 / 升级)

好了,万事俱备只欠东风,跟着强子的脚步,进一步分析 具体创建流程~

具体创建流程

步骤 1:触发 BeanA 的创建

容器调用 AbstractBeanFactory.getBean("beanA")

最终委托 DefaultSingletonBeanRegistry.getSingleton 处理:

  1. 检查一级缓存(singletonObjects):无 BeanA 实例;

  2. 检查二级缓存(earlySingletonObjects):无 BeanA 早期引用;

  3. 检查三级缓存(singletonFactories):无 BeanA 的工厂对象;

  4. 将 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

  1. 检查三级缓存,无 BeanB 相关记录,标记 BeanB 为 创建中

  2. 实例化 BeanB 的原始实例

  3. 将 BeanB 的 ObjectFactory 放入三级缓存

步骤 6:填充 BeanB 的属性

发现 BeanB 依赖 BeanA,调用 getBean("beanA")获取 BeanA

步骤 7:获取 BeanA 的早期引用

再次调用 getSingleton("beanA"):

  1. 检查一级缓存:无;

  2. 检查二级缓存:无;

  3. 检查三级缓存:存在 BeanA 的 ObjectFactory

  4. 调用 ObjectFactory.getObject()生成 BeanA 的早期引用(若需代理则为代理对象,否则为原始对象);

  5. 将 BeanA 的早期引用从三级缓存移至二级缓存,并移除三级缓存中的 BeanA

  6. 返回 BeanA 的早期引用给 BeanB。

为什么执行了 ObjectFactory.getObject()生成的产物是放到二级缓存而不是一级缓存?

二级缓存的存在是为了隔离早期对象完整 Bean ,而一级缓存必须保证存储的是最终可用的单例 Bean

而 ObjectFactory.getObject()生成的产物 未完成:属性填充初始化 ,属于半成品,因此不能放到一级缓存中。

步骤 8:完成 BeanB 的初始化与注册

  1. BeanB 拿到 BeanA 的早期引用,完成属性填充

  2. 执行 BeanB 的初始化逻辑:调用 @PostConstruct 注解方法;执行 InitializingBean.afterPropertiesSet();调用自定义初始化方法(init-method);

  3. 注册 BeanB 到一级缓存,将 BeanB 完整实例放入 singletonObjects

  4. 移除 BeanB 在二级 / 三级缓存的记录,标记 BeanB 为 “创建完成”(从 singletonsCurrentlyInCreation 移除)。

步骤 9:完成 BeanA 的属性填充与初始化

  1. 回到 BeanA 的属性填充步骤,此时 BeanB 已存在于一级缓存,直接注入 BeanB 的完整实例

  2. 执行 BeanA 的初始化逻辑(同 BeanB 的初始化步骤);

  3. 检查 BeanA 是否存在早期引用(二级缓存中)若 BeanA 无需代理:直接使用原始实例;若 BeanA 需代理:早期引用已为代理对象,直接复用;

  4. 注册 BeanA 到一级缓存

  5. 移除 BeanA 在二级缓存的记录,标记 BeanA 为 创建完成(从 singletonsCurrentlyInCreation 移除)

大家会不会杠一下,为什么一定要三级缓存呢?二级行不行?

那就跟强子来深入分析一下为啥一定要三级,少一级都不行的原因~

为什么一定要三级缓存?

第一步:最初的需求

最初的需求,保证单例 Bean 全局唯一,且仅对外暴露 完全初始化完成 的实例(实例化 + 属性填充 + 初始化 + AOP 代理)

此时最朴素的设计是用一个 Map 存储:

// 一级缓存:存储最终可用的单例Beanprivate 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

@Componentpublic class BeanA {    private BeanB beanB;
    // 构造器注入BeanB    public BeanA(BeanB beanB) {        this.beanB = beanB;    }}
@Componentpublic 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 注入

@Componentpublic class BeanA {    private BeanB beanB;    // Setter注入替代构造器注入    @Autowired    public void setBeanB(BeanB beanB) {        this.beanB = beanB;    }}
@Componentpublic class BeanB {    private BeanA beanA;    @Autowired    public void setBeanA(BeanA beanA) {        this.beanA = beanA;    }}
复制代码

Setter 注入允许 Bean 先实例化触发三级缓存注册),再填充依赖,符合 Spring 循环依赖解决方案的流程

方案 2:构造器注入 + @Lazy 延迟加载

@Componentpublic class BeanA {    private BeanB beanB;    // 对构造器依赖加@Lazy,生成代理对象延迟初始化    public BeanA(@Lazy BeanB beanB) {        this.beanB = beanB;    }}
@Componentpublic class BeanB {    private BeanA beanA;    public BeanB(@Lazy BeanA beanA) {        this.beanA = beanA;    }}
复制代码

原理:@Lazy 在构造器注入时生成依赖 Bean 的代理对象,避免实例化阶段的循环等待。

方案 3:使用 ObjectFactory 延迟获取依赖

@Componentpublic class BeanA {    private BeanB beanB;    public BeanA(ObjectFactory<BeanB> beanBFactory) {        // 延迟获取BeanB,避免构造器阶段直接依赖        this.beanB = beanBFactory.getObject();    }}
@Componentpublic class BeanB {    private BeanA beanA;    public BeanB(ObjectFactory<BeanA> beanAFactory) {        this.beanA = beanAFactory.getObject();    }}
复制代码

ObjectFactory 封装依赖获取逻辑,延迟到实际使用时再触发依赖 Bean 的创建

总结

今天,终于把困扰很久的 循环依赖三级缓存 问题 搞清楚了~

主要讲了 循环依赖的 Bean 创建流程 以及 三级缓存的设计思想

还补充了 一些 核心类的总结,使得我们更好的了解 创建流程的内容~

感谢小伙伴给强子留言,也希望解 小伙伴的 疑问 ~

熟练度刷不停,知识点吃透稳,下期接着练~

发布于: 刚刚阅读数: 3
用户头像

DonaldCen

关注

有个性,没签名 2019-01-13 加入

跟我在峡谷学Java 公众号:程序员悟空的宝藏乐园

评论

发布
暂无评论
Java 王者修炼手册【Spring 篇 - 循环依赖 & 三级缓存】:Bean创建流程 + 循环依赖 +  三级缓存原理 大揭秘_循环依赖_DonaldCen_InfoQ写作社区