Spring Bean 的生命周期

  1. 类的生命周期:Java 类什么时候被类加载器加载。
  2. Bean 的生命周期:Spring 什么时候把这个类变成 Bean,并完成注入、初始化、使用、销毁。

很多人会把这两层混在一起。更准确地说:Spring 管的是 Bean 生命周期,JVM 管的是类加载 。Spring 通常是在需要创建 Bean 时,基于 BeanDefinition 去解析并使用这个类。BeanFactory 负责按 BeanDefinition 返回 Bean 实例;单例 Bean 通常会被容器预先创建,原型 Bean 则通常在每次获取时创建。(Home)


总流程图

主线:

erlang 复制代码
类加载
  ↓
Spring 扫描 / 解析配置,注册 BeanDefinition
  ↓
容器启动,执行 BeanFactoryPostProcessor
  ↓
实例化 Bean
  ↓
属性填充(依赖注入)
  ↓
Aware 回调
  ↓
BeanPostProcessor.before
  ↓
初始化方法
  ↓
BeanPostProcessor.after
  ↓
放入单例池 / 开始使用
  ↓
容器关闭
  ↓
销毁前处理
  ↓
销毁方法

这个顺序里,最容易考的就是中间那一段:实例化 → 注入 → Aware → beforeInit → init → afterInit → 使用 → destroy 。Spring 官方明确说明了初始化回调、销毁回调,以及 BeanPostProcessor 是在初始化前后起作用的。(Home)


一、类加载阶段:Bean 还没出生

这里先纠正一个很常见的说法:

不是"Spring 一启动就把所有类都加载了",而是 Spring 先读取配置、扫描组件、注册 BeanDefinition;真正到创建 Bean 时,才会解析并使用对应的类。

BeanFactory 的核心概念是:它持有很多 BeanDefinition,每个 BeanDefinition 描述"这个 Bean 怎么创建"。BeanDefinition 不等于 Bean 实例,它更像"施工图纸"。(Home)

你可以这样理解:

  • 类加载 :JVM 把 .class 文件加载进来
  • BeanDefinition 注册:Spring 记住"这个类将来要作为 Bean"
  • Bean 实例化 :Spring 真正 new 出对象

所以从"类加载到销毁"这个角度看,第一步其实是:

1)类被加载到 JVM

可能是因为组件扫描、配置类解析、反射创建、依赖解析等原因触发。

但此时它还只是一个 Java 类,不一定已经是 Spring Bean。这个阶段更多是 JVM 行为,不是 Spring Bean 生命周期的核心部分。Spring 的核心生命周期真正开始于 BeanDefinition 已经存在,容器准备创建 Bean 。(Home)


二、注册 BeanDefinition:先立档,再造对象

Spring 容器启动时,会先做这些事:

2)读取配置元数据

来源可能是:

  • @Component@Service@Controller
  • @Configuration + @Bean
  • XML
  • Java 配置类

Spring 会把这些信息转成 BeanDefinition 注册到容器中。此时还没有真正创建 Bean,只是告诉容器:"将来这里要有一个 Bean,名字、类型、作用域、初始化方法、销毁方法都在这里。" BeanFactory 的官方定义就强调,它管理的是 BeanDefinition,并根据定义返回实例。(Home)


三、容器级后处理:Bean 出生前,先改"图纸"

在真正创建 Bean 之前,Spring 容器还会先处理一批"容器级扩展点"。

3)执行 BeanFactoryPostProcessor

这一类处理器不是改 Bean 实例,而是改 BeanDefinition

也就是 Bean 还没创建,就先把"施工图纸"改掉。

比如可能做这些事:

  • 修改属性值
  • 替换占位符
  • 调整某些 Bean 定义
  • 解析配置类

这一阶段非常重要,因为它发生在对象实例化之前。虽然你问的是 Bean 生命周期,但从完整过程看,这是 Bean 出生前的关键准备步骤。Spring 文档把这类机制归为容器扩展点。(Home)


四、实例化:Bean 真正被造出来

接下来才进入"Bean 本体生命周期"。

4)实例化 Bean

这一步就是把对象真正创建出来,常见方式有:

  • 构造器实例化
  • 工厂方法实例化
  • @Bean 方法返回对象

你可以把这一步理解成:
Spring 先把"空壳对象"造出来。

注意,这时对象通常只是被创建出来,很多依赖还没注入完,也还没执行初始化逻辑。InstantiationAwareBeanPostProcessor 这类更高级的后处理器,甚至可以在目标 Bean 真正实例化前后介入。Spring 当前文档和 API 都明确提供了 postProcessBeforeInstantiationpostProcessAfterInstantiation 这样的扩展点。(Home)


五、属性填充:给空壳装配零件

5)依赖注入 / 属性填充

对象创建后,Spring 会给它注入依赖,例如:

  • @Autowired
  • @Resource
  • @Value
  • setter 注入
  • 字段注入
  • 构造器注入中剩余的依赖解析

这一步可以理解成"给空壳对象装零件"。

例如 UserService 里依赖 OrderService,Spring 会在这个阶段把 OrderService 注入进去。很多注解功能,本质上就是通过对应的后处理器实现的。Spring 官方文档也说明了:内部会使用 BeanPostProcessor 实现很多回调和注解处理。(Home)


六、Aware 回调:让 Bean 知道"自己在 Spring 里是谁"

6)执行 Aware 接口回调

如果 Bean 实现了这些接口,Spring 会把对应信息注入进去:

  • BeanNameAware
  • BeanClassLoaderAware
  • BeanFactoryAware
  • ApplicationContextAware
  • 以及其他 Aware 接口

比如:

  • 让 Bean 知道自己的名字
  • 拿到类加载器
  • 拿到 BeanFactory
  • 拿到 ApplicationContext

这一步的本质是:
让 Bean 获得 Spring 容器环境信息。

Spring 官方把这些 Aware 接口单独列为生命周期相关能力的一部分。(Home)


七、初始化前置处理:BeanPostProcessor.before

7)执行 postProcessBeforeInitialization

所有注册过的 BeanPostProcessor,都会在初始化方法之前过一遍当前 Bean。

官方定义很明确:
postProcessBeforeInitialization 是在任何初始化回调之前执行,比如 afterPropertiesSet() 或自定义 init-method 之前。(Home)

这一步常做的事:

  • 检查 Bean
  • 包装 Bean
  • 处理注解
  • 补充一些初始化前逻辑

你可以把它理解成:

Bean 已经造好了,零件也装好了,但正式启动前,先过一遍"质检"。


八、初始化:Bean 真正进入可用状态

8)执行初始化方法

这一步是 Bean 生命周期里最经典的一步。Spring 官方给出的初始化机制主要有三种:

  • @PostConstruct
  • InitializingBean.afterPropertiesSet()
  • 自定义 init-method

Spring 官方建议,在现代应用里优先考虑 @PostConstruct;如果不想用注解,也可以用 init-methodInitializingBean 能用,但会让业务代码和 Spring 接口耦合。(Home)

这一步通常做的事:

  • 校验必要参数是否存在
  • 打开连接
  • 预热缓存
  • 加载本地数据
  • 做一些 Bean 真正可运行前的准备

你可以理解成:

前面只是"组装完成",到这里才是"开机启动"。

初始化顺序怎么记?

最常见的记法是:

  1. @PostConstruct
  2. afterPropertiesSet()
  3. 自定义 init-method

Spring 官方文档专门说明了这些初始化回调机制会组合工作。(Home)


九、初始化后置处理:BeanPostProcessor.after

9)执行 postProcessAfterInitialization

初始化完成后,所有 BeanPostProcessor 再过一遍。

官方定义同样很清楚:
postProcessAfterInitialization 是在任何初始化回调之后执行。(Home)

这一阶段尤其重要,因为很多框架能力都在这里实现,比如:

  • AOP 代理
  • 事务代理
  • 日志代理
  • 权限代理

也就是说,容器里最终放进去的,不一定是原始 Bean,本可能是一个代理对象

这就是为什么你写的是 UserService,最后 Spring 给你的可能是一个代理过的 UserService。(Home)

你可以这样记:

beforeInit 是"初始化前检查"

afterInit 是"初始化后包装"


十、进入可用状态:Bean 开始对外服务

10)Bean 放入容器,进入使用阶段

如果是单例 Bean,经过上述流程后,通常会放进单例缓存池,后续别人拿到的就是这个 Bean。

如果是原型 Bean,则通常每次获取都会重新走创建流程,但 Spring 只负责创建,不负责完整销毁管理。BeanFactory 官方也强调了,它根据 BeanDefinition 和作用域语义来管理 Bean 实例。(Home)

如果 Bean 实现了 Lifecycle 等接口,它还可以参与容器整体的启动与停止过程。Spring 官方在生命周期说明中也单独提到了这一点。(Home)


十一、销毁前阶段:容器准备关闭

当容器关闭时,Bean 生命周期进入最后阶段。

11)执行销毁前处理

如果有 DestructionAwareBeanPostProcessor,Spring 会在真正销毁之前先调用它。Spring 当前 API 文档明确说明,这类处理器可以在 Bean 销毁前执行自定义逻辑。(Home)

这一步可以理解成:

正式关机前,再做一次清场和收尾。


十二、销毁阶段:释放资源

12)执行销毁方法

Spring 官方给出的销毁机制主要有三种:

  • @PreDestroy
  • DisposableBean.destroy()
  • 自定义 destroy-method

官方同样建议优先考虑 @PreDestroy,这样不会和 Spring 接口强耦合。(Home)

这一阶段常做的事:

  • 关闭数据库连接
  • 关闭线程池
  • 释放文件句柄
  • 清理缓存
  • 停止定时任务

你可以把它理解成:

Bean 下班前,把占用的资源都还回去。


十三、把整个生命周期串成一句人话

一个 Spring Bean 从头到尾,大致就是:

类先被 JVM 加载 → Spring 先登记成 BeanDefinition → 真正创建对象 → 注入依赖 → 回调各种 Aware 接口 → 初始化前处理 → 执行初始化方法 → 初始化后处理(可能生成代理)→ Bean 开始被使用 → 容器关闭时执行销毁前处理和销毁方法。 (Home)


十四、最容易考的"标准顺序"

如果你是为了面试,建议直接背这一版:

markdown 复制代码
1. 加载 Bean 类
2. 注册 BeanDefinition
3. 执行 BeanFactoryPostProcessor
4. 实例化 Bean
5. 属性填充(依赖注入)
6. 调用 Aware 接口
7. BeanPostProcessor#postProcessBeforeInitialization
8. @PostConstruct
9. InitializingBean#afterPropertiesSet
10. 自定义 init-method
11. BeanPostProcessor#postProcessAfterInitialization
12. Bean 可用
13. 容器关闭
14. @PreDestroy
15. DisposableBean#destroy
16. 自定义 destroy-method

其中第 8 到 10 步和第 14 到 16 步,Spring 官方都明确支持,而且推荐优先用注解方式。(Home)


十五、你要特别注意的 4 个细节

1)BeanDefinition 不是 Bean

它只是"Bean 的说明书",不是实例。(Home)

2)AOP 代理通常发生在初始化后

也就是 postProcessAfterInitialization 之后,你拿到的 Bean 可能已经不是原始对象。(Home)

3)单例和原型生命周期不完全一样

单例 Bean 通常完整走创建和销毁;原型 Bean 通常只保证创建,销毁回调不由容器完整托管。这个差异来自 BeanFactory 的作用域语义。(Home)

4)@PostConstruct / @PreDestroy 通常比实现 Spring 接口更推荐

因为它们更少耦合,更符合官方建议。(Home)

相关推荐
神奇小汤圆2 小时前
我研读了 500 个 Spring Boot 生产级代码库,90% 都犯了这 7 个致命错误
后端
空中海2 小时前
03 MyBatis Spring Boot 集成、事务、测试与工程化体系
spring boot·后端·mybatis
ElonMuscle2 小时前
GO环境速建笔记
后端
用户298698530142 小时前
Java 从零生成 Word 文档:段落、图片与表格操作
java·后端
SimonKing2 小时前
OpenCode 在 IDEA 中使用 ACP 协议 VS 直接使用 TUI,哪个编程方式更是你的菜?
java·后端·程序员
Gopher_HBo2 小时前
Disruptor多生产者多消费者分析
后端
杨运交2 小时前
[013][缓存模块]基于Redis的计数器缓存模板设计——AbstractCounterCacheTemplate 技术解析
spring boot·后端
IVEN_3 小时前
Gradle 依赖下载 403 Forbidden 修复:全局镜像配置实战
android·后端