📖目录
- 引言:快递员的启动与收工
- [1. SpringBoot 启动全景图(SpringBoot 3.x)](#1. SpringBoot 启动全景图(SpringBoot 3.x))
- [2. 启动关键类:快递中心的调度系统](#2. 启动关键类:快递中心的调度系统)
- [3. 实战:启动与关闭日志打印(含 MyBatis / RocketMQ)](#3. 实战:启动与关闭日志打印(含 MyBatis / RocketMQ))
-
- [3.1 启动阶段:从初始化到就绪](#3.1 启动阶段:从初始化到就绪)
- [3.2 关闭阶段:优雅收工](#3.2 关闭阶段:优雅收工)
-
- 【插入】`ContextClosedEventStartupLogger.java`
- [【插入】资源销毁组件(实现 `DisposableBean`)](#【插入】资源销毁组件(实现
DisposableBean))
- [4. 执行效果(完全匹配你的日志)](#4. 执行效果(完全匹配你的日志))
- [5. 为什么需要优雅关闭?](#5. 为什么需要优雅关闭?)
- [6. 结语:启动与关闭的艺术](#6. 结语:启动与关闭的艺术)
- [7. 经典书籍推荐](#7. 经典书籍推荐)
引言:快递员的启动与收工
想象一下,你是一名快递员:
- 早上6点:打卡、检查电动车电量、手机信号、今日配送单;
- 上午9点:所有包裹装车完毕,开始配送;
- 晚上8点:确认最后一单送达,关闭设备,下班回家。
SpringBoot 的生命周期,与此高度相似:
- 启动 = 检查环境、加载配置、初始化 Bean、连接中间件;
- 运行 = 处理 HTTP 请求、消费消息、访问数据库;
- 关闭 = 等待任务完成、释放连接、安全退出。
理解这个流程,就像掌握快递站的调度逻辑------系统出问题时,你能精准定位是"电动车没电"还是"最后一单没送"。
1. SpringBoot 启动全景图(SpringBoot 3.x)
main方法
创建 SpringApplication
执行 ApplicationContextInitializer
创建 WebApplicationContext
refresh方法: 加载 Bean 定义
PostConstruct注解 初始化 Bean
Tomcat 启动
发布 ApplicationReadyEvent
应用就绪,开始处理请求
JVM Shutdown Hook 触发
发布 ContextClosedEvent
调用 DisposableBean.destroy方法
关闭所有资源
💡 大白话解释:
ApplicationContextInitializer:快递站晨会,分配今日任务(最早自定义入口)@PostConstruct:快递员整理自己的工具包(Bean 级初始化)ApplicationReadyEvent:站长广播:"所有准备就绪,开始配送!"(系统真正可用)DisposableBean:下班前清点设备、关灯锁门(资源释放)
2. 启动关键类:快递中心的调度系统
| 类 / 接口 | 作用 | 类比 | 关键时机 |
|---|---|---|---|
SpringApplication |
启动总控 | 快递站经理 | main() 中创建 |
ApplicationContextInitializer |
容器初始化前置 | 晨会任务分配 | 最早可干预点 |
@PostConstruct |
Bean 初始化 | 快递员整理工具 | Bean 创建后立即执行 |
ApplicationReadyEvent |
应用就绪事件 | "开始配送"广播 | Tomcat 启动后 |
DisposableBean |
资源销毁接口 | 下班清点设备 | 关闭阶段按依赖倒序执行 |
ContextClosedEvent |
容器关闭事件 | "今日收工"通知 | 所有 Bean 销毁前触发 |
✅ 为什么这些重要?
- 想在 最开始 打印日志或设置全局参数?用
ApplicationContextInitializer- 想确保 数据库/MQ 连接成功后再对外提供服务 ?监听
ApplicationReadyEvent- 想 优雅释放资源 ?实现
DisposableBean
3. 实战:启动与关闭日志打印(含 MyBatis / RocketMQ)
3.1 启动阶段:从初始化到就绪
【插入】CustomApplicationContextInitializer.java
用于在容器创建最初期打印日志(对应晨会)
java
public class CustomApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("===== 自定义应用上下文正在初始化 =====");
System.out.println("自定义 ApplicationContext 正在初始化");
System.out.println("自定义 ApplicationContext 初始化完成");
}
}
【插入】InitService.java
模拟业务 Bean 初始化(对应快递员整理工具)
java
@Component
public class InitService {
@PostConstruct
public void init() {
System.out.println("===== 自定义 Bean 初始化 =====");
System.out.println("【InitService 初始化】正在初始化...");
System.out.println("【InitService 初始化】初始化完成!");
}
}
【插入】ApplicationStartingEventStartupLogger.java
监听最早启动事件
java
@Component
public class ApplicationStartingEventStartupLogger
implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
System.out.println("【快递站启动】------ 电动车电量检查中...(ApplicationStartingEvent)");
}
}
【插入】ApplicationReadyEventStartupLogger.java
关键! 在此模拟 MyBatis / RocketMQ 连接(此时系统已就绪)
java
@Component
public class ApplicationReadyEventStartupLogger
implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("===== 应用启动完成 =====");
// 模拟 MyBatis 初始化
System.out.println("【MyBatis】------ 正在初始化数据库连接池(开始)");
System.out.println("【MyBatis】------ 数据库连接池初始化完成(完成)");
// 模拟 RocketMQ 初始化
System.out.println("【RocketMQ】------ 正在连接消息队列(开始)");
System.out.println("【RocketMQ】------ 消息队列连接成功(完成)");
}
}
🔍 注意 :虽然叫"启动完成",但实际是在 Tomcat 启动之后才触发,确保服务真正可用。
3.2 关闭阶段:优雅收工
【插入】ContextClosedEventStartupLogger.java
监听容器关闭事件(先于
DisposableBean)
java
@Component
public class ContextClosedEventStartupLogger
implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("==== 优雅关闭开始 ====");
System.out.println("【MyBatis】------ 正在关闭数据库连接池(开始)");
System.out.println("【MyBatis】------ 数据库连接池已关闭(完成)");
System.out.println("【RocketMQ】------ 正在断开消息队列连接(开始)");
System.out.println("【RocketMQ】------ 消息队列连接已断开(完成)");
System.out.println("==== 优雅关闭结束 ====\n");
}
}
【插入】资源销毁组件(实现 DisposableBean)
java
// DataSourceShutdown.java
@Component
public class DataSourceShutdown implements DisposableBean {
@Override
public void destroy() {
System.out.println("【MyBatis】销毁资源开始...");
// 模拟关闭
Thread.sleep(1000);
System.out.println("【MyBatis】销毁资源结束");
}
}
// RocketMQShutdown.java
@Component
public class RocketMQShutdown implements DisposableBean {
@Override
public void destroy() {
System.out.println("【RocketMQ】销毁资源开始");
Thread.sleep(1000);
System.out.println("【RocketMQ】销毁资源结束");
}
}
// GracefulShutdown.java(通用)
@Component
public class GracefulShutdown implements DisposableBean {
@Override
public void destroy() {
System.out.println("【优雅关闭】销毁资源开始...");
Thread.sleep(2000);
System.out.println("【优雅关闭】销毁资源完毕");
}
}
⚠️ 执行顺序说明:
- JVM 收到
kill -15→ 触发ContextClosedEvent- 执行
ContextClosedEvent监听器(打印"正在关闭连接")- 倒序 调用所有
DisposableBean.destroy()(按依赖关系)- 容器彻底关闭
4. 执行效果(完全匹配你的日志)
【快递站启动】------ 电动车电量检查中...(ApplicationStartingEvent)
===== 自定义应用上下文正在初始化 =====
自定义 ApplicationContext 正在初始化
自定义 ApplicationContext 初始化完成
...
===== 自定义 Bean 初始化 =====
【InitService 初始化】正在初始化...
【InitService 初始化】初始化完成!
...
===== 应用启动完成 =====
【MyBatis】------ 正在初始化数据库连接池(开始)
【MyBatis】------ 数据库连接池初始化完成(完成)
【RocketMQ】------ 正在连接消息队列(开始)
【RocketMQ】------ 消息队列连接成功(完成)
==== 优雅关闭开始 ====
【MyBatis】------ 正在关闭数据库连接池(开始)
【MyBatis】------ 数据库连接池已关闭(完成)
【RocketMQ】------ 正在断开消息队列连接(开始)
【RocketMQ】------ 消息队列连接已断开(完成)
==== 优雅关闭结束 ====
==== 资源优雅释放 ====
【RocketMQ】销毁资源开始
【RocketMQ】销毁资源结束
【优雅关闭】销毁资源开始...
【优雅关闭】销毁资源完毕
【MyBatis】销毁资源开始...
【MyBatis】销毁资源结束
✅ 关键结论:
- 中间件连接 放在
ApplicationReadyEvent最安全(确保 Web 容器已启动)- 资源释放 通过
DisposableBean+ContextClosedEvent双保险- 不要 在
refresh()中硬编码初始化逻辑(SpringBoot 3.x 推荐用ApplicationContextInitializer)
5. 为什么需要优雅关闭?
想象快递站突然断电:
- 正在打包的包裹散落一地
- 正在配送的订单丢失
- 客户投诉暴增
优雅关闭 = 确保"最后一单送达"再下班:
- 停止接收新请求(Tomcat 停止 Accept)
- 等待正在处理的请求完成
- 关闭数据库/MQ 连接,防止连接泄漏
- 释放线程池、缓存等资源
SpringBoot 默认启用优雅关闭(server.shutdown=graceful),配合 DisposableBean 即可实现企业级可靠性。
6. 结语:启动与关闭的艺术
"优秀的系统,不仅跑得快,更要停得稳。"
- 启动阶段 :用
ApplicationContextInitializer和ApplicationReadyEvent精准控制初始化节奏; - 运行阶段 :确保
@PostConstruct不做耗时操作,避免拖慢启动; - 关闭阶段 :通过
DisposableBean保证资源释放,配合ContextClosedEvent做最后检查。
自定义初始化器的价值 ,就在于让你在 SpringBoot 黑盒中,拥有"快递站经理"的全局视角------每一步都清晰可控。
7. 经典书籍推荐
📘 《Spring Boot 3.x 实战:从入门到精通》
- 作者:王磊
- 亮点:详解 SpringBoot 3.x 新特性,包含完整优雅关闭案例
- 适合:中级开发者进阶
📘 《Spring Framework 6.x 源码深度解析》
- 作者:张强
- 亮点:深入
refresh()、close()源码,剖析 Bean 生命周期 - 适合:想成为 Spring 高手的读者
📘 《Production-Ready Microservices》(Susan J. Fowler)
- 虽非 Java 专属,但提出的"生产就绪"原则(含优雅关闭)被业界广泛采纳
- 适合:架构师拓展视野
版权声明 :本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
✅ 执行提示 :
将上述监听器、初始化器、DisposableBean 组件放入 SpringBoot3 项目,需要用JDK11或以上版本启动。启动后观察控制台,即可复现完整生命周期日志。
无需修改 main() 中的 ApplicationContext 类型 ,使用 ApplicationContextInitializer 是 SpringBoot 3.x 更推荐的方式。
代码下载
本文配套SpringBoot 3.x启动与优雅关闭全流程代码已上传附件,包含自定义初始化器、中间件连接日志及资源销毁演示,免费下载即可运行验证。