【后端】【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. 结语:启动与关闭的艺术

"优秀的系统,不仅跑得快,更要停得稳。"

  • 启动阶段 :用 ApplicationContextInitializerApplicationReadyEvent 精准控制初始化节奏;
  • 运行阶段 :确保 @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启动与优雅关闭全流程代码已上传附件,包含自定义初始化器、中间件连接日志及资源销毁演示,免费下载即可运行验证。

相关推荐
一点程序10 小时前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
奋进的芋圆12 小时前
Spring Boot 实现三模安全登录:微信扫码 + 手机号验证码 + 邮箱验证码
spring boot·redis·微信
怪兽源码12 小时前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
csdn_aspnet12 小时前
ASP.NET Core 中的依赖注入
后端·asp.net·di·.net core
昊坤说不出的梦13 小时前
【实战】监控上下文切换及其优化方案
java·后端
疯狂踩坑人14 小时前
【Python版 2026 从零学Langchain 1.x】(二)结构化输出和工具调用
后端·python·langchain
m0_7400437315 小时前
【无标题】
java·spring boot·spring·spring cloud·微服务
重整旗鼓~15 小时前
1.外卖项目介绍
spring boot
橘子师兄15 小时前
C++AI大模型接入SDK—ChatSDK封装
开发语言·c++·人工智能·后端
@ chen15 小时前
Spring事务 核心知识
java·后端·spring