【后端】【SpringBoot】① 源码解析:从启动到优雅关闭

📖目录

引言:快递员的启动与收工

想象一下,你是一名快递员:

  • 早上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("【优雅关闭】销毁资源完毕");
    }
}

⚠️ 执行顺序说明:

  1. JVM 收到 kill -15 → 触发 ContextClosedEvent
  2. 执行 ContextClosedEvent 监听器(打印"正在关闭连接")
  3. 倒序 调用所有 DisposableBean.destroy()(按依赖关系)
  4. 容器彻底关闭

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启动与优雅关闭全流程代码已上传附件,包含自定义初始化器、中间件连接日志及资源销毁演示,免费下载即可运行验证。

相关推荐
高频因子挖掘机2 小时前
分钟线跨午休后时间轴不连续,是数据错了还是 A 股交易时段本来如此?
后端·github·api
雪宫街道2 小时前
SpringBoot - POI实现Excel 导入与导出
spring boot·后端·excel
打工仔折腾 AI3 小时前
让时序预测真正参与决策:TimechoAI 工程化落地与管线设计实践
人工智能·后端·python·算法·机器学习·性能优化
CarIise3 小时前
Spring MVC入门与执行流程
后端
雨中飘荡的记忆3 小时前
子代理和 Skills 怎么写?3 个实用例子
后端
IT_陈寒3 小时前
一文搞懂React的核心概念
前端·人工智能·后端
weixin_BYSJ19873 小时前
【计算机毕设】基于Spring_Boot_Vue的马拉松报名与选手成绩追踪系统05445
java·vue.js·spring boot·学习·spring·django·课程设计
高频因子挖掘机3 小时前
多只股票的 K 线日期对不上怎么办?用 Pandas MultiIndex 处理时间序列对齐
后端·github·api
高频因子挖掘机3 小时前
行情 API 返回了数据,为什么量化策略仍然会出错?用分层设计隔离数据故障
后端·github·api
十年Java程序媛12 小时前
Java枚举Enum完整教程|替代静态常量,开发与面试要点
java·开发语言·spring boot·后端·面试