当前位置: 首页 > news >正文

揭秘Spring Boot内嵌Tomcat原理

tomcat 介绍

tomcat 是 web容器(servlet 容器),不管请求是访问静态资源HTML、JSP还是java接口,对tomcat而言,都是通过servlet访问:

  • 访问静态资源,tomcat 会交由一个叫做DefaultServlet的类来处理。
  • 访问 JSP,tomcat 会交由一个叫做JspServlet的类来处理。
  • 访问 Servlet ,tomcat 会交由一个叫做 InvokerServlet的类来处理。

所谓 jsp 就是 html 加上 java 代码片段,JspServlet 最终输出的也是 html 而已。

tomcat 启动 spring 项目

了解 springboot 内嵌 tomcat 启动原理之前,应该了解“祖先” spring 怎么启动和加载上下文的,对 springboot 的理解才深刻。

web.xml配置

spring 启动必须依赖 web 容器,这里以 tomcat 举例。

spring 项目中简单的 web 配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/root-context.xml</param-value> </context-param> <servlet> <servlet-name>app1</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/app1-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>app1</servlet-name> <url-pattern>/app1/*</url-pattern> </servlet-mapping> 
</web-app>

spring 怎么启动?

tomcat 通过调用 spring 中的 servlet 对象(DispatcherServlet),然后调用该对象的 init() 方法,作为入口启动spring。

如何调用到 spring 中的 servlet 对象,分为以下两种:

  • web.xml 配置文件方式:tomcat 通过 web.xml 配置文件中 servlet 标签指定的 servlet 类路径,反射生成 servlet 对象。
  • java config 方式:没有 web.xml 配置文件了,项目中必须实现 WebApplicationInitializer 接口,并现实 WebApplicationInitializeronStartup 方法,在 onStartup 方法中创建 dispacherServlet 并指定使用。

上述 java config 方式衍生出另一个问题:WebApplicationInitializer 的实现类怎么被 tomcat 调用到呢?

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {......}
}
  • tomcat 会通过SPI机制,找到 ServletContainerInitializer 接口的所有实现类,然后反射生成对象,并轮流执行实现类中 onStartup 方法。SpringServletContainerInitializer 就是 ServletContainerInitializer 实现类之一。
  • SpringServletContainerInitializer 类上有@HandlesTypes({WebApplicationInitializer.class})注释,这个注释是 servlet 规范中的,tomcat 会通过字节码加载技术(ASM),找到注解中指定类及子类(WebApplicationInitializer.class 及子类),然后将其作为参数传给 SpringServletContainerInitializeronStartup方法使用。我们自定义的WebApplicationInitializer 实现类就这样被调用到了 。

所以我们实现了 WebApplicationInitializer 接口,就会被 tomcat 加载。

怎么加载 springContext?

tomcat 通过调用 DispatcherServlet 对象的 init() 方法,加载 springContext 上下文,所以 DispatcherServlet 对象内有一个字段存放 springContext。

若配置多个 servlet,springContext 有几个?

  • 每个 servlet 创建过程中,都会创建 springcontext,各 servlet 都有自己的上下文。
  • 所以各 servlet 的 init-param 标签中,指定的扫描路径如果有重复的,重复的 bean 对象也会在各 servlet 的 springcontext 中新建,不会共用。
  • 那么重复的 bean 对象是不是有点浪费内存呢?确实如此,所以在多个servlet的情况下,需要配 listener 标签,表示配置父容器(root context)。可以合理配置父容器加载 service 和 repository 层的 bean,这部分可以共用,而 controller 层的 bean 是在各 servlet 中的子容器中加载,因为涉及到servlet路由嘛。

不过一般都不会有多个servlet,通常常规项目中 web.xml 中 listener 标签根本就不需要配置。

springboot 启动 tomcat

启动 web 容器

上述说的 spring 启动,必须依赖 web 容器启动。由 web 容器通过 SPI 机制,加载 spring 自己的 servlet DispatcherServlet,再通过 servlet 对象创建 springContext

而 springboot 不需要外部 web 容器了,那它怎么监听端口接收请求呢,难道 springboot 内部又重新写了一个 servlet?当然不是,看下面代码:

@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// 同时会创建 web 容器。onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}......}......
}

上述代码片很熟悉吧,如果不熟的查查spring初始化context流程,这里不做详细描述。
主要看 onRefresh() 方法,该方法启动容器的流程大概如下,以 tomcat 为例:

  • onRefresh() 方法内,会调用 createWebServer() 方法,createWebServer() 方法内会调用 tomcat||jetty||undertow jar 包依赖提供的方法,来创建 web 容器并启动。
  • 比如 tomcat,它是这样创建:new Tomcat()
  • 创建容器后,对 tomcat 容器的Connector进行配置,并将 DispatcherServlet 添加到 tomcat 容器中。
  • 最后 tomcat.start() 启动容器。

tomcat\jetty\undertow springboot用哪个?

我们了解到了 springboot 会调用 createWebServer() 方法,创建“合适”的 web 容器。

  • 那 springboot 怎么判断该创建 tomcat、jetty 还是 undertow 容器呢?

实际上就是在 createWebServer() 方法里面判断的,该方法代码如下:

private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {// 选择用哪个容器ServletWebServerFactory factory = getWebServerFactory();// 创建及启动 web 容器this.webServer = factory.getWebServer(getSelfInitializer());getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer));getBeanFactory().registerSingleton("webServerStartStop",new WebServerStartStopLifecycle(this, this.webServer));}else if (servletContext != null) {try {getSelfInitializer().onStartup(servletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context", ex);}}initPropertySources();
}

上述代码片,getWebServerFactory() 方法会获得 tomcat||jetty||undertowServletWebServerFactory, 用这个 factory 对象就能创建对应的 web 容器。

而在 getWebServerFactory() 方法内是按类型从 SpringContext 中获取 ServletWebServerFactory 类型的 bean。

所以只要 SpringContext 注入什么容器的 ServletWebServerFactory,springboot 就会启动什么容器。

  • 什么地方注入 ServletWebServerFactory 呢?

ServletWebServerFactoryConfiguration 这个配置类,将tomcat||jetty||undertowServletWebServerFactory 注入进 springContext,配置类伪代码如下:

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {   // tomcat 的 factory@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)static class EmbeddedTomcat {@BeanTomcatServletWebServerFactory tomcatServletWebServerFactory( ... ) { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();... return factory;}}// Jetty 的 factory@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)static class EmbeddedJetty {@BeanJettyServletWebServerFactory JettyServletWebServerFactory( ... ) { JettyServletWebServerFactory factory = new JettyServletWebServerFactory();... return factory;}}// Undertow 的 factory.......
}

可以看出只要引入了某 web 容器的依赖,对应的 @ConditionalOnClass 就能满足,该 web 容器的 ServletWebServerFactory 就会被注入进 springboot。

  • 那项目如果引入了多个 web 容器依赖,springboot 使用哪一个?

还想使用哪个??getWebServerFactory() 方法内就直接报错了:Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : xxx

  • 我们平时使用 tomcat ,那 jetty、undertow 等容器依赖便不会引入。那上述 ServletWebServerFactoryConfiguration 类的代码,@ConditionalOnClass 中某些类肯定找不到,运行时不会报错吗?

首先应该知道 spring 怎么判断是否是 bean 对象?常人理解 spring 是通过 JVM 反射获取类注解信息,来确定是否反射生成 bean 对象注入 SpringContext 中。

但实际 spring 并不是这样判断的,如果通过 JVM 获取类信息,那不是启动前要把所有类都加载一次,这和 JVM 用时加载的思想冲突了。所以spring 是通过 ASM 技术从 class 字节码文件中获取注解信息,来判断是否是需要的 bean。

之所以没有依赖也不会报错,是因为spring 会通过 ASM 技术取出 @ConditionalOnClass 注解中所有的 values后,会用 ClassLoader 尝试加载这些 values,如果加载不到,catch住异常使其不会报错,同时这个类也被认为不符合注入条件,不会生成对象注入 springcontext。所以没引入所有 web 容器依赖也不会报错。

相关文章:

揭秘Spring Boot内嵌Tomcat原理

tomcat 介绍 tomcat 是 web容器&#xff08;servlet 容器&#xff09;&#xff0c;不管请求是访问静态资源HTML、JSP还是java接口&#xff0c;对tomcat而言&#xff0c;都是通过servlet访问&#xff1a; 访问静态资源&#xff0c;tomcat 会交由一个叫做DefaultServlet的类来处…...

分类散点图 stripplot() 加辅助线axhline() 多图合一

分类散点图 stripplot 加辅助线axhline 多图合一 效果图代码 画图没有什么可说的&#xff0c;直接上图 效果图 代码 # 绘制图&#xff0c; 查看是否数值在阈值上 plt.figure(figsize(30, 18)) n 0 for header, value_list in info_dict.items():ref_value_list ref_info_dic…...

一文告诉你为什么时序场景下 TDengine 数据订阅比 Kafka 好

在 TDengine 3.0 中&#xff0c;我们对流式计算、数据订阅功能都进行了再升级&#xff0c;帮助用户极大简化了数据架构的复杂程度&#xff0c;降低整体运维成本。TDengine 提供的类似消息队列产品的数据订阅、消费接口&#xff0c;本质上是为了帮助应用实时获取写入 TDengine 的…...

reg与wire的用法,证明reg可以在右边,wire型在左边,来作组合逻辑处理。

reg与wire的用法&#xff0c;证明reg可以在右边&#xff0c;wire型在左边&#xff0c;来作组合逻辑处理。 1&#xff0c;RTL2&#xff0c;生成的原理图 1&#xff0c;RTL 参考文献&#xff1a; 1&#xff0c;verilog 中 wire 和reg 的使用 2&#xff0c;解决一个assign问题&…...

Studio One6.2简体中文免费最新版本宿主软件

对于一些有创作需求的朋友来说&#xff0c;为自己写的歌制作伴奏是很平常的。今天要和大家分享的就是自己写的歌怎么做伴奏&#xff0c;自己做伴奏的软件有哪些。Studio One是宿主软件界的一个后起之秀&#xff0c;推出的时间不久&#xff0c;但是受到了大量音乐制作人的推崇。…...

算法刷题 week2

目录 week21. 二维数组中的查找题目题解(单调性扫描) O(nm) 2.替换空格题目题解(线性扫描) O(n)(双指针扫描) O(n) 3.从尾到头打印链表题目题解(遍历链表) O(n) week2 1. 二维数组中的查找 题目 题解 (单调性扫描) O(nm) 核心在于发现每个子矩阵右上角的数的性质&#xff1…...

子网的划分

强化计算机网络发现王道没有这一块的内容&#xff0c;导致做题稀里糊涂。于是个人调研补充。 子网划分是将一个大型IP网络划分成更小的子网&#xff0c;以实现更有效的网络管理和资源分配。 原因&#xff1a; 提高网络性能&#xff1a;子网划分可以减少广播域的大小&#xff…...

Docker安装与卸载

Docker安装与卸载 安装 yum install -y yum-utils \device-mapper-persistent-data \lvm2 --skip-broken更新本地镜像源 打开终端或 SSH 连接到 Rocky Linux 的服务器。 进入 /etc/yum.repos.d/ 目录&#xff0c;该目录包含 Rocky Linux 的 yum 配置文件。 cd /etc/yum.repo…...

【Davinci开发】:开发过程问题记录及总结

开发过程问题总结 1、SWC访问系统OS Timer返回值异常a、代码发现,RTE接口为未连接状态b、连接后,仍然有问题,单步调试,发现没有访问权限当新平台基于之前平台的代码而延续开发时(应用代码相同,但是芯片已经更换),记录开发过程中遇所到的问题,单步调试,逐一排查。 1、…...

数据结构——排序算法——冒泡排序

冒泡排序1 void swap(vector<int> arr, int i, int j) {int temp arr[i];arr[i] arr[j];arr[j] temp;}void bubbleSort1(vector<int> arr) {for (int i 0; i < arr.size() - 1; i){for (int j 0; j < arr.size() - 1 - i; j){if (arr[j] > arr[j 1…...

vscode使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…...

python经典百题之求前!的和

题目&#xff1a;求12!3!…20!的和 方法一&#xff1a; 使用for循环和阶乘函数计算每项的值&#xff0c;再将每项的值累加起来。 def factorial(n):if n 0:return 1else:return n * factorial(n-1)sum 0 for i in range(1, 21):sum factorial(i) * iprint(sum)优点&#…...

C语言入门Day_22 初识指针

目录 前言&#xff1a; 1.内存地址 2.指针的定义 3.指针的使用 4.易错点 5.思维导图 前言&#xff1a; 之前我们学过变量可以用来存储数据&#xff0c;就像一个盒子里面可以放不同的球一样。 这是一个方便大家理解专业概念的比喻。 在计算机世界里面&#xff0c;数据实…...

【面试必刷TOP101】删除链表的倒数第n个节点 两个链表的第一个公共结点

目录 题目&#xff1a;删除链表的倒数第n个节点_牛客题霸_牛客网 (nowcoder.com) 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目&#xff1a;两个链表的第一个公共结点_牛客题霸_牛客网 (nowcoder.com) …...

手刻 Deep Learning -第壹章 -PyTorch教学-激励函数与感知机入门(上)

一、前言 本文接续前篇教学 Pytorch 与线性回归 &#xff0c;本文着重在 Activation Function &#xff08; 中文称 激励函数 &#xff09;&#xff0c;我们会介绍激励函数 &#xff08;也有人称 激活函数&#xff1f; 激发函数&#xff1f; &#xff09; 为什么会有用&#xf…...

物理内存分配

目录 内核物理内存分配接口 内存分配行为&#xff08;物理上&#xff09; 内存分配的行为操作 内存 三个水位线 水线计算 水位线影响内存分配行为 内存分配核心__alloc_pages 释放页 1、内核物理内存分配接口 struct page *alloc_pages(gfp_t gfp, unsigned int ord…...

RFID产线自动化升级改造管理方案

应用背景 在现代制造业中&#xff0c;产线管理是实现高效生产和优质产品的关键环节&#xff0c;产线管理涉及到生产过程的监控、物料管理、工艺控制、质量追溯等多个方面&#xff0c;有效的产线管理可以提高生产效率、降低成本、改善产品质量&#xff0c;并满足市场需求的变化…...

全量数据采集:不同网站的方法与挑战

简介 在当今数字化时代中&#xff0c;有数据就能方便我们做出很多决策。数据的获取与分析已经成为学术研究、商业分析、战略决策以及个人好奇心的关键驱动力。本文将分享不同网站的全量数据采集方法&#xff0c;以及在这一过程中可能会遇到的挑战。 部分全量采集方法 1. 撞店…...

Redis——渐进式遍历和数据库管理命令

介绍 如果使用keys * 这样的操作&#xff0c;将Redis中所有的key都获取到&#xff0c;由于Redis是单线程工作&#xff0c;这个操作本身又要消耗很多时间&#xff0c;那么就会导致Redis服务器阻塞&#xff0c;后续的操作无法正常执行 而渐进式遍历&#xff0c;通过多次执行遍历…...

如何打造可视化警务巡防通信解决方案

近年来&#xff0c;科学技术飞速发展&#xff0c;给予了犯罪分子可乘之机。当面临专业化的犯罪分子、高科技的犯罪手段&#xff0c;传统警务模式似乎不能满足警方打击犯罪的需要&#xff0c;因此当今公安工作迫切需要构建智能化、系统化、信息化的警务通信管理模式。 警务人员…...

ATF(TF-A) SPMC威胁模型-安全检测与评估

安全之安全(security)博客目录导读 ATF(TF-A) 威胁模型汇总 目录 一、简介 二、评估目标 1、数据流图 三、威胁分析 1、信任边界 2、资产 3、威胁代理 4、威胁类型 5、威胁评估 5.1 端点在直接请求/响应调用中模拟发送方或接收方FF-A ID 5.2 篡改端点和SPMC之间的…...

BIO AIO NIO 的区别

BIO AIO NIO 是 Java 中用于 I/O 操作的三种不同的编程模型。它们的区别在于它们执行I/O 操作的方式和效率。在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念&#xff1a;同步与异步&#xff0c;阻塞与非阻塞。 同步与异步 同步&#xff1a;同步就是发起一个调用后&#xff…...

大数据学习1.1-Centos8网络配置

1.查看虚拟网卡 2.配置网络信息 打勾处取消 记住箭头的数字 3.修改 网络连接 4.进入虚拟网络 5.进入属性 6.修改IPv4 5.将iIP和DNS进行修改 6.配置网络信息-进入修改网络配置文件 # 进入root用户 su root # 进入网络配置文件 cd /etc/sysconfig/network-scripts/ # 修改网络配…...

在Android studio 创建Flutter项目运行出现问题总结

在Android studio 中配置Flutter出现的问题 A problem occurred configuring root project ‘android’出现这个问题。解决办法 首先找到flutter配置的位置 在D:\xxx\flutter\packages\flutter_tools\gradle位置中的flutter.gradle buildscript { repositories { googl…...

Ceph入门到精通-ceph对于长文件名如何处理

RADOS object with short name 上一篇博文&#xff0c;我们将介绍了对象相关的数据结构ghobject_t&#xff0c;以及对象在底层文件系统存储的文件名&#xff0c;以及如何从文件名对应到 ghobject_t对象。 映射关系如下图所示&#xff1a; 这里面有一个漏洞&#xff0c;即obje…...

vue+element-ui 项目实战示例详解【目录】

vue 和 element是两个流行的前端即时&#xff0c;通常用于管理后台&#xff0c;PC等页面 能够快速构建美观的界面 1. vue2 介绍 Vue.js是一个流行的JavaScript框架&#xff0c;用于构建用户界面。它的版本分为Vue 2和Vue 3&#xff0c;而Element是一个基于Vue.js 2的UI组件库。…...

性能测试-性能调优主要方向和原则(15)

性能调优主要方向明确性能瓶颈之后,就需要进行性能调优了,调优主要从图所示的多个方向入手。能优化手段并不一定是独立应用的,在一次优化过程中很可能应用了多种优化技巧。 硬件层面优化 硬件层面优化更偏向于监控,当定位到硬件资源成为瓶颈后,更多是采用扩容等手段来解决…...

关于taos数据库使用过程中突发“unable to establish connection”问题解决

项目使用的版本信息 1.taos的版本信息 3.0.4.1 2.jdbc的版本 3.2.1 3.druid连接池版本 1.2.11问题描述 Java应用服务连接&#xff0c;突然大量抛出如下的异常信息导致应用宕机&#xff1a; sql: select server_status(), desc: unable to establish connection和集团DBA沟通…...

【Qt】Qt中将字符串转换为数字类型的函数总结以及用法示例

在Qt中&#xff0c;可以使用以下函数将字符串转换为数字类型&#xff1a; toInt()&#xff1a;将字符串转换为int类型。toDouble()&#xff1a;将字符串转换为double类型。toFloat()&#xff1a;将字符串转换为float类型。toLongLong()&#xff1a;将字符串转换为long long类型…...

效率工具3-计算机网络工具

查看各个状态的tcp连接情况 netstat -n | awk ‘/^tcp/ {S[$NF]} END {for(a in S) print a, S[a]}’ /^tcp/ 正则表达式匹配 netstat 命令输出的匹配部分&#xff0c;即以 "TCP" 开始的行{S[$NF]} 对于符合条件的每一行&#xff0c;awk 命令将使用数组 S 来计算每…...

网站怎么不要钱自己做/大数据

MapReduce 1. MapReduce 思想 2.MapReduce的设计构思 3.MapReduce的编写流程 运行模式...

wordpress 远程管理/2023引流软件

php 图片局部打马赛克 原理&#xff1a; 对图片中选定区域的每一像素&#xff0c;添加若干宽度及高度&#xff0c;生成矩型。而每一像素的矩型重叠在一起。就形成了马赛克效果。本例使用GD库的imagecolorat获取像素颜色&#xff0c;使用imagefilledrectangle画矩型。效果图&…...

政府单位网站建设方案/关键词搜索广告

java8的stream流学习(2) ***前言*** 之前也写过两三篇关于Stream相关的帖子,当然了,也是参考的.我感觉java8的新特性还是要深刻掌握的,因为这几个新特性的确能帮助我们让代码变得健壮,不说了,直接写案例,撸代码 参考网址: https://mp.weixin.qq.com/s/IHkpqdRLeEPAgdPbOnxsKw …...

wordpress 多级菜单/百度网站推广一年多少钱

A. loginB. rc.dC. initD. startup14. 在Linux系统中&#xff0c;硬件设备大部分是安装在______目录下的。 BA. /mntB. /devC. /procD. /swap15. 比较重要的系统配置资料&#xff0c;一般来说大部分位于____目录下&#xff0c;如果是进行升级安装&#xff0c;最好先备份。 BA. …...

有哪些网站可以做图片打赏/郑州百度推广外包

qDeleteAll&#xff1a;专门用于指针容器&#xff0c;对容器或者迭代器中的每个对象进行delete操作&#xff0c;而不是从容器中移除对象。源代码如下&#xff1a; void qDeleteAll(ForwardIterator begin, ForwardIterator end) { while (begin ! end) { delete *begin; begin;…...

企业品牌网站建设注意事项/最新黑帽seo教程

在上一篇中&#xff0c;我们基本已经把整个框架都搭建出来了&#xff0c;下面进行代码重构一下。 思路&#xff1a; 导航按钮&#xff0c;按下时&#xff0c;会变灰&#xff0c;那是系统自带了&#xff0c;通过自定义UIButton,实现按下按钮立即切换效果。MJTabBarController管得…...