Spring Boot源码分析——支持外部 Tomcat 容器的实现详解
yuyutoo 2024-10-26 16:08 7 浏览 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 ”即可获取。(注:转发分享,感谢大家)
相关推荐
- 12、高阶组件:魔法增幅器——React 19 HOC模式
-
一、魔法增幅器的本质"高阶组件是魔法师用咒语叠加的炼金术,"霍格沃茨魔咒研究院院长凝视着发光的增幅器,"通过函数式能量场的嵌套,让基础组件获得预言家日报式的逻辑继承!"...
- 深入理解nodejs的异步IO与事件模块机制
-
一、node为什么要使用异步I/O异步最先诞生于操作系统的底层,在底层系统中,异步通过信号量、消息等方式有广泛的应用。但在大多数高级编程语言中,异步并不多见,这是因为编写异步的程序不符合人习惯的思维逻...
- 前端时间同步利器:React + useEffect 实现高性能动态时钟
-
前言在你奋笔疾敲代码的瞬间,是不是突然一低头,发现时间像偷偷跑路的变量,一眨眼就从上午飘到下午?饭没吃、会没开、工位也快被前端猫霸占了。仿佛你写的不是代码,而是“时间穿梭机”。别慌,咱们今天就来用R...
- JavaScript 异步编程指南 - 聊聊 Node.js 中的事件循环
-
作者:五月君来源:编程界|事件循环是一种控制应用程序的运行机制,在不同的运行时环境有不同的实现,上一节讲了浏览器中的事件循环,它们有很多相似的地方,也有着各自的特点,本节讨论下Node.js中...
- 10个Vue开发技巧「实践」
-
作者:WahFung转发链接:https://juejin.im/post/5e8a9b1ae51d45470720bdfa路由参数解耦一般在组件内使用路由参数,大多数人会这样做:...
- 通过番计时器实例学习 React 生命周期函数 componentDidMount
-
大家好,今天我们将通过一个实例——番茄计时器,学习下如何使用函数生命周期的一个重要函数componentDidMount():componentDidMount(),在组件加载完成,render之后...
- 前端必看!10 个 Vue3 救命技巧,解决你 90% 的开发难题?
-
写Vue3项目时,是不是总被数据更新延迟、组件间传值混乱、页面加载缓慢这些问题折磨得头秃?别担心!作为摸爬滚打多年的老前端,今天掏出压箱底的10个实战技巧,从性能优化到复杂逻辑处理,每一个都能...
- 如何用2 KB代码实现3D赛车游戏?2kPlus Jam大赛了解一下
-
选自frankforce作者:Frank机器之心编译参与:王子嘉、GeekAI控制复杂度一直是软件开发的核心问题之一,一代代的计算机从业者纷纷贡献着自己的智慧,试图降低程序的计算复杂度。然而,将一款...
- 证明你访问的网站是你想访问的,Safari 真的需要
-
安全研究员在Safari上找到了一个新漏洞,能让网站在浏览器的地址栏内将自己伪装成另一个网站——得益于Safari地址栏的“智能缩略”功能。在Deusen最近公开的攻击演示(PoC,P...
- 抓狂!TS 组件性能拉胯到崩溃?4 个绝杀技巧逆风翻盘!
-
前端兄弟姐妹们五一假期快乐,咱们谁还没被TypeScript组件的性能问题折磨过?页面加载转圈圈,点击按钮没反应,代码改了一轮又一轮,性能却还是原地踏步,分分钟想砸电脑!别慌,今天这4个绝杀技...
- 让小球做圆周运动,你有几种办法?
-
最近在阅读外国技术文章中无意中发现了一个神奇的CSS属性motion-path,它可以让Dom元素可以按照自定义的路径移动。又想起了很久之前参加校招面试的时候,面试官问了我一个问题“能不能不借助库实现...
- 前端基础进阶(十四):深入核心,详解事件循环机制
-
EventLoopJavaScript的学习零散而庞杂,很多时候我们学到了一些东西,但是却没办法感受到进步!甚至过了不久,就把学到的东西给忘了。为了解决自己的这个困扰,在学习的过程中,我一直在试图寻...
- 从0搭建一个WebRTC,实现多房间多对多通话,并实现屏幕录制
-
这篇文章开始会实现一个一对一WebRTC和多对多的WebRTC,以及基于屏幕共享的录制。本篇会实现信令和前端部分,信令使用fastity来搭建,前端部分使用Vue3来实现。为什么要使用WebRTCWe...
- Vue2 开发卡壳?这 10 个实战技巧专治各种不服
-
干前端开发的兄弟,谁还没被Vue2折腾过?数据不更新、组件通信乱成麻、性能差到想砸电脑……这些痛点,我都懂!今天直接甩出10个超实用的实战技巧,每一个都是从项目“血坑”里爬出来总结的,专...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
-
- 12、高阶组件:魔法增幅器——React 19 HOC模式
- 深入理解nodejs的异步IO与事件模块机制
- 前端时间同步利器:React + useEffect 实现高性能动态时钟
- JavaScript 异步编程指南 - 聊聊 Node.js 中的事件循环
- 10个Vue开发技巧「实践」
- 通过番计时器实例学习 React 生命周期函数 componentDidMount
- SRE监控四大黄金指标,任何一个有异常都会是灾难……
- 前端必看!10 个 Vue3 救命技巧,解决你 90% 的开发难题?
- 如何用2 KB代码实现3D赛车游戏?2kPlus Jam大赛了解一下
- 证明你访问的网站是你想访问的,Safari 真的需要
- 标签列表
-
- 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)