一篇讲清楚Spring Bean生命周期:设计思路 + 一个小例子

第一次看 Spring Bean 生命周期时,都会被一堆“步骤 / 接口 / 扩展点”搞懵:看完只剩下背诵,真写业务又不会用。其实它的核心就是把 “一个对象从出生到死亡” 这套流程标准化,并且在关键节点留出“钩子”,让你能把资源初始化、清理这些事放到正确的时间点执行。


先想一个“普通 Java 对象的生命周期”

在 Java 里创建并使用一个对象,抽象成几步就够了:

  1. 创建对象:new一个对象出来,但属性可能还是默认值(null、0…)。
  2. 属性赋值:给对象里的各种属性填上值。
  3. 初始化:属性有了以后,可能需要“基于这些属性做进一步准备”,比如计算派生属性、初始化资源(连接、文件、线程池等)。
  4. 使用对象:业务逻辑真正开始用它。
  5. 销毁对象:系统要停了 / 容器要关了,对象销毁前往往要释放资源、落库、关闭连接等。

一个最直观的例子:数据库连接池

  • 创建:new 一个连接池对象。
  • 赋值:设置 URL、用户名、密码等配置。
  • 初始化:根据这些配置先创建若干连接,放进池里备用。
  • 使用:业务从池里借连接、归还连接。
  • 销毁:系统退出前关闭连接、释放资源。

Spring Bean 生命周期,本质上就是把这套流程搬进容器里,并且提供标准化扩展点。

Spring Bean 生命周期

Spring Bean 生命周期核心流程也可以记成五段:

  1. 实例化:Spring 通过反射创建 Bean 实例(把对象“扭出来”)。

  2. 属性赋值:依赖注入发生在这里,比如 @Autowired 注入属性,本质就是容器把依赖找出来,再通过反射 / setter / 构造器把值塞进去。

  3. 初始化:让 Bean 在“可用之前”做最后准备。Spring 把初始化拆成三段,是为了给框架/中间件留扩展点(比如 AOP 代理、统一增强、注解解析等),而业务开发通常只需要记住它们的相对位置:

    ①初始化前置处理:BeanPostProcessor 的前置回调(before)

    ②初始化本身:真正写的初始化逻辑,例如:@PostConstruct 、配置 init-method

    ③初始化后置处理:BeanPostProcessor 的后置回调(after)

    对写业务最关键的一点:初始化方法(@PostConstruct / init-method)触发时,依赖注入通常已经完成,所以你可以安全使用 @Autowired 注入进来的依赖去做准备工作。

  4. 使用:业务代码通过注入或从容器获取 Bean 并使用。

  5. 销毁:容器关闭时,Bean 在消亡前完成资源释放、收尾工作。

    • 实现接口DisposableBean#destroy()
    • 配置方法destroy-method

真正需要掌握的是:

(1) 什么时候属性已经注入完成?(初始化阶段开始之前)

(2) 什么时候适合初始化资源?(初始化回调里)

(3) 什么时候适合释放资源?(销毁回调里)

一个完整小例子:文件记录工具

需求:

  • 系统运行过程中:把一些数据记录到本地文件
  • 系统运行结束时:把文件路径 + 文件大小保存到数据库,方便后续查看

设计:

  • 写一个 FileRecord 类负责记录文件
  • 它依赖一个 FileService,负责把文件信息存到数据库
  • 生命周期落点:
    • 初始化阶段:创建本地文件(@PostConstruct
    • 销毁前阶段:把文件路径和大小存库(@PreDestroy

下面是一个示意代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Component
public class FileRecord {

// 【阶段2:属性赋值】Spring 在实例化之后把容器里的 FileService 注入进来
@Autowired
private FileService fileService;

// 【阶段3:初始化】
@PostConstruct
public void init() {
System.out.println("本地创建一个文件");
// 这里可以执行:创建文件、打开流、初始化缓冲区等
}

// 【阶段4:使用】
public void record() {
System.out.println("给文件里写入一些数据");
// 这里可以执行:写文件逻辑
}

// 【阶段5:销毁】
@PreDestroy
public void destroy() {
System.out.println("获取文件名,和文件大小,并保存到数据库");
fileService.saveFilepathAndSize();
// 这里可以执行:关闭流、flush、释放句柄等
}
}