Spring源码解析系列(四)Spring Bean 的循环依赖原理(三级缓存)深入分析

共计 2105 个字符,预计需要花费 6 分钟才能阅读完成。

一、前言

在使用 Spring 时,你是否遇到过如下错误?

Requested bean is currently in creation: Is there an unresolvable circular reference?

这说明你踩到了 循环依赖 的坑。但你可能不知道的是,Spring 为了解决这个问题,内置了一套精妙的机制 —— 三级缓存机制

本文将深入源码、图示原理、揭示 Spring 是如何解决构造器注入无法处理、但 setter 注入能正常工作的循环依赖问题。

二、什么是循环依赖?

1. 定义

循环依赖是指在依赖注入过程中,两个或多个 Bean 相互引用,形成闭环。

例如:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

当 Spring 实例化 A 时,需要注入 B,而 B 又需要注入 A,此时就出现了“鸡生蛋,蛋生鸡”的困境。

Spring 能解决哪些循环依赖?

注入方式 是否支持循环依赖
构造器注入 ❌ 不支持
Setter 注入 ✅ 支持
@Autowired 字段注入 ✅ 支持

三、Spring 是怎么解决循环依赖的?

1. 三级缓存结构

Spring 利用 三级缓存机制 解决循环依赖问题。它们存在于 DefaultSingletonBeanRegistry 中:

/** 一级缓存:成品 Bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

/** 二级缓存:早期暴露的 Bean(不含依赖注入和增强) */
private final Map<String, Object> earlySingletonObjects = new HashMap<>();

/** 三级缓存:暴露 ObjectFactory,用于创建早期 Bean 引用 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

流程图:

Spring源码解析系列(四)Spring Bean 的循环依赖原理(三级缓存)深入分析

四、源码分析:如何一步步解决循环依赖

源码位置:AbstractAutowireCapableBeanFactory#doCreateBean

1.创建 Bean 实例(仅构造方法)

instanceWrapper = createBeanInstance(beanName, mbd, args);

这时的 Bean 是裸实例,尚未依赖注入。

2.添加到三级缓存

if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

注册一个 ObjectFactory,供后续在 B 中注入 A 时使用。

3.属性填充(依赖注入)

populateBean(beanName, mbd, instanceWrapper);

此时如果 B 中需要 A,Spring 调用 getBean("A"),先查缓存:

  • 一级缓存查不到
  • 二级缓存查不到
  • 查三级缓存 → 调用 ObjectFactory 创建早期引用(原始 A)

于是 B 顺利拿到 A 的早期引用,完成注入。

4.初始化 Bean(初始化方法、增强处理)

此时 B 中的 A 是一个未完成初始化的早期 Bean(可能未增强、未执行 init 方法),不过完成了循环。

五、三层缓存对比总结

缓存层级 类型 作用说明
singletonObjects 一级缓存 存放完全初始化完成的 Bean(成品)
earlySingletonObjects 二级缓存 存放早期暴露的 Bean 引用(成品前)
singletonFactories 三级缓存 存放生成早期 Bean 的工厂(ObjectFactory)

缓存流程图:

singletonFactories (三级缓存)
    ↓ 创建早期引用
earlySingletonObjects (二级缓存)
    ↓ 完成初始化后
singletonObjects (一级缓存)

六、构造器注入为何不支持循环依赖?

因为构造器注入在构造方法中就需要依赖注入完成,而 Spring 只有在构造方法执行后才能暴露 ObjectFactory,因此无法提前注入。

执行构造器时 Bean 尚未被暴露到三级缓存,因此会抛出循环依赖异常。

如需开启,则需要加上配置

spring.main.allow-circular-references=true

七、关注后续专栏

本文是《Spring 源码解析系列》专栏的第 4 篇,后续将发布:

  • 《Spring 中的事件监听机制(ApplicationEvent)源码与实战》

附录:核心源码入口

  • AbstractBeanFactory#getBean
  • DefaultSingletonBeanRegistry#getSingleton
  • AbstractAutowireCapableBeanFactory#doCreateBean
  • addSingletonFactory()
  • populateBean()

如果本文对你有帮助,欢迎点赞、评论、收藏!我是 李卷卷,专注Java相关知识输出。感谢阅读!

正文完
 1
李卷卷
版权声明:本站原创文章,由 李卷卷 于2023-07-29发表,共计2105字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)