Spring Bean 生命周期 & 循环依赖:90% 开发者只知结论不懂原理

Spring Bean 生命周期 & 循环依赖:90% 开发者只知结论不懂原理

"Spring 能解决 setter 循环依赖,解决不了构造器循环依赖。"

这句话,几乎所有 Java 后端都能背出来。

但问一句:为什么?底层是怎么做到的?三级缓存到底存了什么?

绝大多数人就开始支支吾吾了。

这篇文章,我们不背结论,一起把 Spring Bean 生命周期 + 循环依赖的底层机制​ 拆清楚。读完你会明白:

Spring 不是"神奇地"解决了循环依赖,而是"精心设计"了一套流程。


一、先跳出细节:Bean 生命周期全景图(简化版)

为了讲清楚循环依赖,必须先有"地图"。

一个 Bean 从定义到可用,核心经历以下几个阶段:

csharp 复制代码
实例化(new)
   ↓
填充属性(populate)
   ↓
初始化(init)
   ↓
就绪(getBean)
   ↓
销毁(destroy)

对应到 Spring 源码中,最核心的方法是:

bash 复制代码
AbstractAutowireCapableBeanFactory#doCreateBean

里面三件大事,务必记住:

  1. createBeanInstance:创建实例(反射 new)
  2. populateBean:依赖注入(@Autowired / setter)
  3. initializeBean:执行 aware、init-method、AOP 代理

👉 循环依赖的问题,就发生在「实例化之后、属性注入期间」


二、什么是循环依赖?为什么是个问题?

1️⃣ 什么是循环依赖

less 复制代码
@Service
public class A {
    @Autowired
    private B b;
}

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

创建 A → 需要 B → 创建 B → 又需要 A → 无限套娃。

2️⃣ 问题的本质

对象还没创建完,就被别人引用了。

  • 如果等 A 完全创建好再给 B,B 拿不到
  • 如果先给 B 一个"半成品 A",又怕出问题

Spring 的解法很巧妙:

👉 提前暴露一个"尚未初始化完成的 Bean 引用"


三、三级缓存:Spring 解决循环依赖的核心

这是面试必考点,也是理解原理的关键。

1️⃣ 三级缓存分别是什么?

缓存名称 作用
singletonObjects 一级缓存:存放完全初始化好的 Bean
earlySingletonObjects 二级缓存:存放早期暴露的 Bean(未初始化完)
singletonFactories 三级缓存:存放 ObjectFactory(Bean 的工厂)

2️⃣ 它们的关系(重点)

  • 一级缓存:最终对外暴露的 Bean
  • 二级缓存:防止重复创建早期引用
  • 三级缓存真正解决循环依赖的关键

注意:三级缓存里存的不是 Bean 本身,而是一个 工厂(ObjectFactory)


四、源码级流程:Spring 是如何"破局"的?

我们用 A → B → A​ 这个经典循环依赖,走一遍流程。

Step 1:创建 A

scss 复制代码
getBean(A)
  • singletonObjects 中没有 A
  • 标记 A 正在创建中
  • 调用 createBeanInstance(A)实例化 A(此时 a 是半成品)

Step 2:将 A 的 ObjectFactory 放入三级缓存

scss 复制代码
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))

这一步非常关键:

  • A 虽然没初始化完
  • 但 Spring 已经允许别人拿到它的早期引用

Step 3:填充 A 的属性(需要 B)

scss 复制代码
populateBean(A)
→ getBean(B)

Step 4:创建 B

  • singletonObjects 中没有 B
  • 实例化 B
  • 同样把 B 的 ObjectFactory 放入三级缓存

Step 5:填充 B 的属性(需要 A)

scss 复制代码
populateBean(B)
→ getBean(A)

这时候重点来了 👇


Step 6:B 再次 getBean(A) 时发生了什么?

Spring 发现:

  • A 正在创建中 ✅
  • 三级缓存中存在 A 的 ObjectFactory ✅

于是:

  1. 调用 ObjectFactory.getObject()
  2. 拿到 A 的 早期引用
  3. 放入 二级缓存(earlySingletonObjects)
  4. 删除三级缓存中的工厂

👉 B 成功拿到了"半成品 A"


Step 7:B 初始化完成

  • B 初始化完成
  • 放入 一级缓存 singletonObjects

Step 8:回到 A 的创建流程

  • A 继续填充属性(此时 B 已就绪)
  • A 初始化完成
  • 放入一级缓存
  • 清理二、三级缓存

循环依赖解决完成


五、为什么 setter / field 注入可以,构造器注入不行?

这是很多人"背结论"的地方,现在我们从原理上解释。

setter / field 注入为什么可以?

因为流程是这样的:

css 复制代码
实例化 A(new A)✅
→ 暴露早期引用 ✅
→ 再注入 B ✅

实例化 和 依赖注入 是分开的

Spring 有机会在"注入前"先把 A 曝光出去。


构造器注入为什么不行?

kotlin 复制代码
@Service
public class A {
    private final B b;
    public A(B b) { this.b = b; }
}

构造器注入的流程是:

css 复制代码
创建 A
→ 必须先有 B
→ 才能 new A

问题来了:

  • A 还没实例化完
  • 根本来不及放进三级缓存
  • B 再来要 A → 找不到 → 抛异常

📌 结论一句话版

构造器注入把"实例化"和"依赖注入"绑死了,而 Spring 的循环依赖机制依赖"先实例化、后暴露"。


六、那 AOP 呢?代理对象会不会出问题?

这是一个进阶但非常重要的问题。

如果 A 需要被 AOP 代理:

  • 早期暴露的是 原始对象
  • 但最终放入容器的是 代理对象

Spring 怎么保证一致性?

答案就在这一句:

scss 复制代码
getEarlyBeanReference()
  • 如果 Bean 需要被代理
  • 三级缓存中的 ObjectFactory
  • 会提前生成代理对象

这样:

  • B 拿到的就是 代理后的 A
  • 最终容器中也是代理后的 A
  • ✅ 完全一致

七、面试标准答案(可直接背)

Spring 通过三级缓存解决 singleton 作用域下的 setter / field 循环依赖。

在 Bean 实例化完成后、属性注入前,Spring 会将一个 ObjectFactory 放入三级缓存,从而在发生循环依赖时,能让对方拿到当前 Bean 的早期引用。

构造器循环依赖无法解决,因为构造器调用发生在实例化阶段,此时 Bean 尚未放入三级缓存,无法满足提前暴露的条件。


八、实际工作中的三点建议

  1. 不要依赖循环依赖

    • 循环依赖往往是设计坏味道
    • 优先考虑:拆分职责、引入中间层、使用 @Lazy
  2. 构造器注入优先

    • 虽然不支持循环依赖
    • 但能更早暴露设计问题
    • 更符合"不可变对象"思想
  3. 理解 > 背诵

    • 面试不是为了复述结论
    • 而是展示你对 Spring 容器运作机制的掌控力

九、总结一句话

Spring 不是"黑魔法",它只是在 Bean 生命周期的关键节点,提前暴露了一个工厂,让半成品 Bean 有了被引用的机会。

当你能把这句话讲清楚,你也就超越了那 90% 只知结论的开发者。

相关推荐
IT_陈寒20 分钟前
Vue的响应式什么时候会失灵?这个坑我踩了
前端·人工智能·后端
Patrick_Wilson34 分钟前
Web 认证方案技术指南
前端·后端·面试
与驴OO1 小时前
设计 Skill 系统,这 3 个坑我替你踩过了
后端·aigc
爱勇宝2 小时前
客户只想看个页面,我却做了一个静态演示发布系统
前端·javascript·后端
一个有理想的摸鱼选手2 小时前
(四)路书Agnet-综合天气距离交通节奏等多因素来编排旅行路线
前端·后端·gis
神奇的程序员2 小时前
在这个独属于ai的时代,我终究是被裁员了
前端·后端·面试
云边有个稻草人2 小时前
从单机工具到“云+端+服务”:KDMS数据库迁移工具如何支撑大型信创项目协同作战
后端
李广坤2 小时前
AgentScope Tool 热更新技术方案:不重启服务,实时管控 Agent 工具
后端·架构
ttwuai2 小时前
Go 后台接入 SSO 后菜单正常但接口 403,怎么排查权限链路?
开发语言·后端·golang
65岁退休Coder3 小时前
LangChain v1.3.4 笔记 - 08 MCP & 相关概念
后端·python·langchain