Spring Boot源码分析——支持外部 Tomcat 容器的实现详解
yuyutoo 2024-10-26 16:08 3 浏览 0 评论
概述
我们知道 Spring Boot 应用能够被打成 war 包,放入外部 Tomcat 容器中运行。你是否知道 Spring Boot 是如何整合 Spring MVC 的呢?
如何使用
在我们的 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 自动配置注解的实现原理
如果文章对你有帮助的话,就点赞支持一波吧,非常感谢!
如何获取Java学习资料?
转发分享此文,后台私信小编:“ 666 ”即可获取。(注:转发分享,感谢大家)
相关推荐
- 史上最全的浏览器兼容性问题和解决方案
-
微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...
-
- 平面设计基础知识_平面设计基础知识实验收获与总结
-
CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...
-
2025-02-21 16:01 yuyutoo
- 写作排版简单三步就行-工具篇_作文排版模板
-
和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...
- 写一个2048的游戏_2048小游戏功能实现
-
1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...
- 今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……
-
最近的天气暖和得让很多小伙伴们喊“热”!!! 昨天的气温到底升得有多高呢?你家有没有榜上有名?...
- CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式
-
之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...
- 你的自我界限够强大吗?_你的自我界限够强大吗英文
-
我的结果:A、该设立新的界限...
- 行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?
-
行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...
-
- 让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
-
去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...
-
2025-02-21 16:00 yuyutoo
- 今年国家综合性消防救援队伍计划招录消防员15000名
-
记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...
- 一起盘点最新 Chrome v133 的5大主流特性 ?
-
1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...
- 竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使
-
style="text-align:center;"data-mce-style="text-align:...
- 学物理能做什么?_学物理能做什么 卢昌海
-
作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...
-
- 你不知道的关于这只眯眼兔的6个小秘密
-
在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...
-
2025-02-21 16:00 yuyutoo
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)