Spring Boot 源码分析 - 支持外部 Tomcat 容器的实现
参考 知识星球 中 芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读
Spring Boot 版本:2.2.x
最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章
如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~
该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》
概述
我们知道 Spring Boot 应用能够被打成 war
包,放入外部 Tomcat 容器中运行。你是否知道 Spring Boot 是如何整合 Spring MVC 的呢?
在上一篇 《Spring Boot 内嵌 Tomcat 容器的实现》 文章中分析了 Spring Boot 白打成 jar
包后是如何创建 Tomcat 容器并启动的,那么这篇文章主要告诉你 Spring Boot 应用被打成 war
包后放入外部 Tomcat 容器是如何运行的。
如何使用
在我们的 Spring Boot 项目中通常会引入 spring-boot-starter-web
这个依赖,该模块提供全栈的 WEB 开发特性,包括 Spring MVC 依赖和 Tomcat 容器,我们将内部 Tomcat 的 Starter 模块排除掉,如下:
然后启动类这样写:
这样你打成 war
包就可以放入外部的 Servlet 容器中运行了。
实现原理
原理在分析 Spring MVC 源码的时候讲过,参考我的 《精尽Spring MVC源码分析 - 寻找遗失的 web.xml》 这篇文章
借助于 Servlet 3.0 的一个新特性,新增的一个 javax.servlet.ServletContainerInitializer
接口,在 Servlet 容器启动时会通过 Java 的 SPI 机制从 META-INF/services/javax.servlet.ServletContainerInitializer
文件中找到这个接口的实现类,然后调用它的 onStartup(..)
方法。
在 Spring 的 spring-web
模块中该文件是这么配置的:
一起来看看这个类:
通过 @HandlesTypes
注解指定只处理 WebApplicationInitializer
类型的类
这个过程很简单,实例化所有 WebApplicationInitializer
类型的对象,然后依次调用它们的 onStartup(ServletContext)
方法

通过打断点你会发现,有一个 DemoApplication 就是我们的启动类
这也就是为什么如果你的 Spring Boot 应用需要打成 war
包放入外部 Tomcat 容器运行的时候,你的启动类需要继承 SpringBootServletInitializer
这个抽象类,因为这个抽象类实现类 WebApplicationInitializer
接口,我们只需要继承它即可
SpringBootServletInitializer
org.springframework.boot.web.servlet.support.SpringBootServletInitializer
抽象类,实现了 WebApplicationInitializer
接口,目的就是支持你将 Spring Boot 应用打包成 war
包放入外部的 Servlet 容器中运行
在 onStartup(ServletContext)
方法中就两步:
- 调用
createRootApplicationContext(ServletContext)
方法,创建一个 WebApplicationContext 作为 Root Spring 应用上下文 - 添加一个 ContextLoaderListener 监听器,会监听到 ServletContext 的启动事件,因为 Spring 应用上下文在上面第
1
步已经准备好了,所以这里什么都不用做
第 1
步是不是和 Spring MVC 类似,同样创建一个 Root WebApplicationContext 作为 Spring 应用上下文的父对象
createRootApplicationContext 方法
createRootApplicationContext(ServletContext)
方法,创建一个 Root WebApplicationContext 对象,如下:
过程如下:
-
创建一个 SpringApplication 构造器,目的就是启动 Spring 应用咯
-
设置
mainApplicationClass
,也就是你的启动类,主要用于打印日志 -
从 ServletContext 上下文中获取最顶部的 Root ApplicationContext 应用上下文
parent
,通常这里没有父对象,所以为空 -
如果
parent
不为空,则先 ServletContext 中的该属性置空,因为这里会创建一个 ApplicationContext 作为 Root- 添加一个
ApplicationContextInitializer
初始器,用于设置现在要创建的 Root ApplicationContext 应用上下文的父容器为parent
- 添加一个
-
添加一个
ApplicationContextInitializer
初始器,目的是往 ServletContext 上下文中设置 Root ApplicationContext 为现在要创建的 Root ApplicationContext 应用上下文,并将这个 ServletContext 保存至 ApplicationContext 中注意,这个对象很关键,会将当前 ServletContext 上下文对象设置到 ApplicationContext 对象里面,那么后续就不会再创建 Spring Boot 内嵌的 Tomcat 了
-
设置要创建的 Root ApplicationContext 应用上下文的类型(Servlet)
-
对 SpringApplicationBuilder 进行扩展,调用
configure(SpringApplicationBuilder)
方法,这也就是为什么我们的启动类可以重写该方法,通常不用做什么 -
添加一个 ApplicationListener 监听器,用于将 ServletContext 中的相关属性关联到 Environment 环境中
-
构建一个 SpringApplication 对象
application
,用于启动 Spring 应用 -
如果没有设置
source
源对象,那么这里尝试设置为当前 Class 对象,需要有@Configuration
注解 -
因为 SpringApplication 在创建 ApplicationContext 应用上下文的过程中需要优先注册
source
源对象,如果为空则抛出异常 -
添加一个错误页面 Filter 作为
sources
-
调用
application
的run
方法启动整个 Spring Boot 应用
整个过程不复杂,SpringApplication 相关的内容在前面的 《SpringApplication 启动类的启动过程》文章中已经分析过,这里的关键在于第 5
步
添加的 ServletContextApplicationContextInitializer
会将当前 ServletContext 上下文对象设置到 ApplicationContext 对象里面
ServletContextApplicationContextInitializer
可以看到会将这个 ServletContext 上下文对象设置到 ApplicationContext 中
那么我们回顾到上一篇 《Spring Boot 内嵌 Tomcat 容器的实现》 文章的 1. onRefresh 方法小节调用的 createWebServer()
方法,如下:
我们看到上面第 4
步,如果从当前 Spring 应用上下文获取到了 ServletContext 对象,不会走上面的第 3
步,也就是不创建 Spring Boot 内嵌的 Tomcat
主动调用它的 getSelfInitializer()
方法来往这个 ServletContext 对象中注册各种 Servlet、Filter 和 EventListener 对象,包括 Spring MVC 中的 DispatcherServlet 对象,该方法参考上一篇 《Spring Boot 内嵌 Tomcat 容器的实现》 文章的 2. selfInitialize 方法 小节
总结
本文分析了 Spring Boot 应用被打成 war
包后是如何支持放入外部 Tomcat 容器运行的,原理也比较简单,借助 Spring MVC 中的 SpringServletContainerInitializer
这个类,它实现了 Servlet 3.0 新增的 javax.servlet.ServletContainerInitializer
接口
-
通过 Java 的 SPI 机制,在
META-INF/services/javax.servlet.ServletContainerInitializer
文件中写入SpringServletContainerInitializer
这个类,那么在 Servlet 容器启动的时候会调用这个类的onStartup(..)
方法,会找到WebApplicationInitializer
类型的对象,并调用他们的onStartup(ServletContext)
方法 -
在我们的 Spring Boot 应用中,如果需要打成
war
包放入外部 Tomcat 容器运行,启动类则需要继承SpringBootServletInitializer
抽象类,它实现了WebApplicationInitializer
接口 -
在
SpringBootServletInitializer
中会创建一个 WebApplicationContext 作为 Root Spring 应用上下文,同时会将 ServletContext 对象设置到 Spring 应用上下文中 -
这样一来,因为已经存在 ServletContext 对象,那么不会再创建 Spring Boot 内嵌的 Tomcat 容器,而是对 ServletContext 进行一些初始化工作
好了,到这里关于 Spring Boot 启动 Spring 应用的整个主流程,包括内嵌 Tomcat 容器的实现,以及支持运行在外部 Servlet 容器的实现都分析完了
那么接下来,我们一起来看看 @SpringBootApplication
这个注解,也就是 @EnableAutoConfiguration
自动配置注解的实现原理
__EOF__

本文链接:https://www.cnblogs.com/lifullmoon/p/14957634.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义