1598 字
8 分钟
Spring Bean 生命周期与循环依赖:回调顺序、三级缓存和代理

Bean 生命周期讨论一个对象怎样成为可用组件,容器启动讨论整个应用怎样组装。先区分实例化和初始化,再理解为什么循环依赖需要提前引用尚未完成初始化的对象。

普通 Bean 的生命周期#

以下是成功创建普通 Bean 的主线,假设相关注解处理器已注册,不把所有特殊扩展路径混在一起。

实例化:调用构造器
→ 属性填充:解析并注入依赖
→ Aware 等回调
→ 初始化前的 BeanPostProcessor 处理
└─ 常见 @PostConstruct 回调在相应处理器中执行
→ InitializingBean.afterPropertiesSet
→ 自定义 init-method
→ 初始化后的 BeanPostProcessor 处理,通常可创建 AOP 代理
→ 使用
→ 容器关闭时执行适用的销毁回调

实例化只是创建对象,初始化是在依赖等准备后执行初始化逻辑。Aware 接口用于让 Bean 获得容器相关信息,例如 BeanNameAware;不同 Aware 由不同机制处理,不必背成所有接口都在同一个固定回调点执行。

BeanFactoryPostProcessor 处理定义等容器元数据,BeanPostProcessor 处理创建中的实例;前者不是每个业务 Bean 自身的生命周期回调。

初始化和销毁回调的顺序#

对分别配置的不同回调方法,常见顺序是:

阶段常见顺序
初始化@PostConstruct → afterPropertiesSet → 自定义初始化方法
销毁@PreDestroy → DisposableBean.destroy → 自定义销毁方法
@Component
public class ResourceBean implements InitializingBean, DisposableBean {
@PostConstruct
public void prepare() { /* 依赖注入后准备资源 */ }
@Override
public void afterPropertiesSet() { /* 初始化校验 */ }
@PreDestroy
public void stop() { /* 停止接收工作 */ }
@Override
public void destroy() { /* 释放持有资源 */ }
}

这里为了展示顺序同时使用几种方式,普通业务通常选择合适的一种,不需堆叠重复逻辑。现代 Spring 常使用 jakarta.annotation,旧项目可能使用 javax.annotation,按项目版本选择。

单例的适用销毁回调由容器关闭时管理;prototype 创建后通常交给调用方,容器不会自动持续追踪并执行它的销毁回调。进程被强制终止也不能保证优雅销毁,所以关键状态不能只依赖关闭回调保存。

为什么会有循环依赖?#

下面用单例字段注入演示,前提是容器允许这类循环。代码用于理解机制,不作为推荐业务设计。

@Component
class A {
@Autowired B b;
}
@Component
class B {
@Autowired A a;
}

A 的对象已能创建,但注入 B 时又需要 A。如果只能获取完全创建好的 Bean,就会不断互相等待。Spring 对部分单例场景允许提前暴露已经实例化、尚未完成依赖注入和初始化的引用。

三级缓存分别放什么?#

这些是单例创建协调结构,不是三级业务数据缓存。

常见名称保存内容作用
singletonObjects完成创建流程的单例对象正常获取成品 Bean
earlySingletonObjects已生成的提前引用循环中复用同一个提前引用
singletonFactories产生提前引用的工厂需要时生成引用,给代理等处理留出机会

一个基本流程是:

  1. 实例化 A,注册能够产生 A 提前引用的工厂。
  2. A 注入 B,开始创建 B。
  3. B 注入 A,成品缓存没有 A;在创建中查找提前引用。
  4. 必要时调用 A 的工厂,生成并缓存提前引用,再注入 B。
  5. B 完成创建,注入 A;A 完成创建,最终对象进入成品缓存,相关提前缓存清理。

提前拿到引用不等于 A 已初始化。不要在 B 的初始化逻辑里随意调用 A 并假设所有依赖都准备完毕。

为什么使用工厂,而不直接放原始对象?#

提前引用可能需要经过 getEarlyBeanReference 等扩展处理。例如 A 需要 AOP 代理,B 应尽量拿到与最终对外一致的代理,而不是未经增强的原始 A。

工厂让容器在确实需要提前引用时再做相关处理,配合自动代理创建器协调提前代理与最终暴露对象。它不是唯一理论实现方式,也不是“三级缓存天然能解决所有 AOP 循环”;自定义后置处理器等仍可能导致提前引用与最终对象不一致并创建失败。

哪些情况不能直接解决?#

  • 纯构造器循环:创建 A 前先需要 B,创建 B 前又需要 A,A 还未实例化,无法靠这一套暴露已有对象。
  • prototype 循环:不使用同样的单例缓存协调机制,不能直接套用。
  • 不允许循环的配置:Spring Boot 2.6 起默认禁止循环依赖,机制存在不代表项目允许使用。
  • 部分代理或生命周期组合:提前引用和最终对象不一致时,可能失败。

优先提取共同职责、调整调用方向或用合适事件协作消除循环。@Lazy 等延迟引用可在部分场景改变触发时机,但不能保证所有设计和初始化问题都被消除。

面试回答模板#

Bean 通常经过实例化、注入、初始化前后处理和初始化回调,再进入使用与销毁。常见初始化回调顺序是 PostConstruct、afterPropertiesSet、自定义初始化方法,销毁有对应顺序。部分单例循环依赖可以利用实例化后的提前引用解决:成品缓存、提前引用缓存、引用工厂分别管理不同阶段,工厂给提前代理留下处理机会。构造器、prototype、配置禁用或代理不一致等情况不能一概保证解决。

自测#

  1. B 拿到 A 的提前引用,A 是否必然已完成初始化?不是。
  2. 构造器循环为何不能直接用同样机制解决?缺少可提前暴露的已实例化对象。
  3. 三级缓存中的工厂一定直接返回原始对象吗?不一定,可通过扩展提供提前代理。
  4. prototype Bean 会由容器自动执行所有销毁回调吗?通常不会,调用方要管理释放。
Spring Bean 生命周期与循环依赖:回调顺序、三级缓存和代理
https://blog.xqcherry.top/posts/interview/spring/spring-bean-lifecycle-and-circular-dependencies/
作者
xqcherry
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0