Spring Boot 项目完整生命周期

点击运行到手动关闭期间发生了什么?

总结

Spring Boot 生命周期概括:从点击启动到关闭,本质上是一个“环境准备 -> 创建 Spring 容器 -> 启动内嵌 Tomcat -> Bean 实例化与依赖注入 -> 接收请求 -> 优雅停机与资源销毁”的串行流水线。


详细原理

1. 为什么 Tomcat 线程池能“绑架” Spring 的对象?

在传统的 WAR 包部署模式下,Tomcat 是“宿主”,Spring 只是跑在 Tomcat 肚子里的一个“应用”。

  • Spring 的管辖权:Spring 的 ApplicationContext 只能管理声明了 @Bean@Service 的单例。当应用重载时,Spring 会调用这些单例的 @PreDestroy 方法并清空引用。
  • Tomcat 的特权:Tomcat 为了维持高并发,它的工作线程池(Worker Threads)是随着 Tomcat 启动就建立的,生命周期和 JVM 进程一样长。当你把一个对象塞进 ThreadLocal 时,这个对象就被强行塞进了 Tomcat 线程的私有 ThreadLocalMap 里。Spring 根本无权去清空 Tomcat 线程内部的数据。所以 Spring 容器就算自毁了,旧对象依然被 Tomcat 线程死死抱着。

2. Spring Boot 项目完整生命周期拆解

Spring Boot 的核心思想是“反客为主”——不再是把 Spring 塞进 Tomcat,而是 Spring Boot 启动时,自己去 new 一个内嵌的 Tomcat。完整流程如下:

  • 第一阶段:环境准备(Environment Setup)
    执行 SpringApplication.run()。JVM 启动,加载操作系统的环境变量、JVM 启动参数、以及你的 application.yml/properties,构建出一个统一的 Environment 对象。
  • 第二阶段:创建上下文(Context Creation)
    根据当前是不是 Web 环境,推断并实例化对应的 Spring 容器(通常是 AnnotationConfigServletWebServerApplicationContext)。
  • 第三阶段:启动内嵌 Web 服务器(Web Server Start)
    (Spring Boot 的精髓点) 容器刷新(refresh() 方法)的早期,Spring 会去寻找并实例化 ServletWebServerFactory(默认是 Tomcat)。此时,Tomcat 被作为 Spring 的一个小弟拉起来,绑定了 8080 端口,但此时还不能处理请求,因为 Spring 业务 Bean 还没准备好。
  • 第四阶段:Bean 的生命周期(Bean Lifecycle)
    这是面试最爱考的区域。Spring 扫描你的 @RestController@Service,读取类元数据。然后依次进行:实例化(new 出对象) -> 属性填充(@Autowired 依赖注入) -> 初始化(执行 @PostConstruct) -> 生成 AOP 代理类(利用 CGLib 增强,比如你的声明式事务 @Transactional
  • 第五阶段:对外接客(Application Ready)
    所有单例 Bean 创建完毕,Spring 容器初始化完成。发布 ApplicationReadyEvent 事件。内嵌 Tomcat 的连接器正式开始接收外部的 HTTP 请求,并转发给 Spring 的 DispatcherServlet
  • 第六阶段:优雅停机(Graceful Shutdown)
    当你按下 Ctrl+C 或发送 kill -15 信号时。Spring Boot 注册的 ShutdownHook(关闭钩子)被触发。它会先拒绝 Tomcat 的新请求,然后依次调用所有单例 Bean 的销毁逻辑(关闭 Redis 客户端、释放 HikariCP 数据库长连接),最后关闭 Spring 容器本身。

实际的例子

假设你的应用是一个极简的“用户注册”服务。

启动与运行态:

  1. 你点击 IDE 的 Run 按钮。控制台喷出炫酷的 Spring 字符画。
  2. 看到日志:Tomcat initialized with port(s): 8080 (http)。此时底层的 C++ 层面已经监听了系统的 8080 端口。
  3. 接着看到:HikariPool-1 - Starting...。Spring 正在根据生命周期实例化数据库连接池的 Bean,并和 MySQL 建立了 10 个 TCP 长连接。
  4. 看到:Started Application in 3.456 seconds。此时,系统正式活了。
  5. 前端发来一个 /register 的 POST 请求,内嵌 Tomcat 的 http-nio-8080-exec-1 线程接手,穿过拦截器,最终调用到你的 UserService.register() 方法完成落库。

关闭态:

  1. 你在控制台点了红色的停止按钮(模拟正常关闭)。
  2. 控制台立刻打出:Closing Spring root WebApplicationContext
  3. 紧接着打出:HikariPool-1 - Closing...。这是 Spring 在执行单例 Bean 的销毁逻辑,主动向 MySQL 发送 QUIT 命令,断开那 10 个 TCP 连接,避免产生 MySQL 服务端的死连接。
  4. 最终 JVM 进程安全退出。
posted @ 2026-03-12 17:01  Nickey103  阅读(60)  评论(0)    收藏  举报