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

嵌入式 Tomcat 与 Spring MVC 集成

在 Spring Boot 普及的今天,“内嵌服务器” 早已成为开发者习以为常的特性 —— 无需安装独立 Tomcat,只需一个main方法就能启动 Web 应用。但鲜少有人深入探究:Spring Boot 的内嵌 Tomcat 究竟是如何通过代码启动的?Spring MVC 又如何与 Tomcat 底层组件联动?Servlet 的生命周期在嵌入式场景下有何不同?

本文将基于你提供的嵌入式 Tomcat 启动代码,从 Tomcat 核心架构、启动流程、Spring 集成到 Servlet 底层机制,逐层拆解嵌入式 Web 应用的运行本质,帮你打通 “代码实现” 到 “底层原理” 的认知链路。

代码地址

一、前置知识:Tomcat 核心架构与嵌入式场景的适配

在分析代码前,必须先理解 Tomcat 的经典架构 —— 这是嵌入式场景的 “骨架”。你的代码注释中已经给出了关键架构图,我们在此基础上进一步拆解:

Server (Tomcat实例)
└── Service (业务逻辑封装)├── Connector (请求入口:监听端口、处理协议)└── Engine (请求处理引擎)└── Host (虚拟主机:默认localhost)└── Context (Web应用上下文:对应一个独立应用)└── WEB-INF (配置、类、依赖)

传统 Tomcat 与嵌入式 Tomcat 的核心差异

  • 传统 Tomcat:通过webapps目录部署 WAR 包,容器自动解析 WAR 生成 Context;
  • 嵌入式 Tomcat:通过 Java 代码手动创建ServerConnectorContext等组件,无需 WAR 包,完全由代码控制生命周期。

二、嵌入式 Tomcat 启动全流程:代码与原理的深度绑定

TomcatEmbeddedRunner类中,startServer方法是整个启动流程的核心。我们按步骤拆解每个环节的 “代码行为” 与 “底层逻辑”,带你看清 Tomcat 如何从无到有。

2.1 第一步:创建 Tomcat 实例与工作目录配置

Tomcat tomcatServer = new Tomcat();
tomcatServer.setBaseDir("embedded-tomcat-base");
底层原理:
  • Tomcat 实例(Server)new Tomcat()本质是创建了一个StandardServer实例(Tomcat 对Server接口的默认实现),它是 Tomcat 的 “顶层容器”,负责管理Service组件和整体生命周期。
  • 工作目录(baseDir):默认情况下,Tomcat 会使用系统临时目录(如/tmp)存储日志、临时文件、Session 数据等。通过setBaseDir指定自定义目录(embedded-tomcat-base),可以避免系统清理临时文件导致的异常,同时便于日志排查。

2.2 第二步:配置 Connector:请求入口与 NIO2 协议

Connector httpConnector = new Connector(new Http11Nio2Protocol());
httpConnector.setPort(8080);
tomcatServer.setConnector(httpConnector);
底层原理:
  • Connector 的角色Connector是 Tomcat 的 “请求大门”,负责监听指定端口、解析 HTTP 协议、将请求传递给Engine处理,并将响应返回给客户端。一个Service可以有多个Connector(如同时监听 8080 HTTP 和 8443 HTTPS)。

  • Http11Nio2Protocol:为何选择 NIO2?

    Tomcat 支持三种协议实现:

    • BIO(阻塞 I/O):一个请求对应一个线程,高并发下线程耗尽,性能差;
    • NIO(非阻塞 I/O):基于 Reactor 模式,少量线程处理大量请求,性能提升;
    • NIO2(异步 I/O):在 NIO 基础上支持异步操作,进一步减少线程阻塞,是嵌入式场景的最优选择(Spring Boot 默认也用 NIO2)。
  • 端口绑定setPort(8080)会将Connector与 8080 端口绑定,底层通过 Java 的ServerSocket实现监听。

2.3 第三步:构建 Context:Web 应用的 “专属容器”

File docBaseDir = Files.createTempDirectory("spring-boot-webapp-").toFile();
docBaseDir.deleteOnExit();
Context tomcatContext = tomcatServer.addContext("", docBaseDir.getAbsolutePath());
底层原理:
  • Context 的核心作用Context是 Tomcat 对 “单个 Web 应用” 的抽象,每个Context对应一个独立的应用(如传统 Tomcat 中webapps下的一个 WAR 包)。它管理应用的 Servlet、Filter、Listener、静态资源等。
  • docBase:Web 应用的根目录传统 Tomcat 中,docBase是 WAR 包解压后的目录(含WEB-INF);而在纯 Java 配置的嵌入式场景中,我们无需静态资源(如 HTML、CSS),因此用临时目录即可。deleteOnExit()确保 JVM 退出时删除临时目录,避免磁盘残留。
  • Context Path:应用访问路径addContext("", ...)的第一个参数是空字符串,表示该Context是 “默认应用”,访问路径为http://localhost:8080/;若改为/app,则访问路径为http://localhost:8080/app/

2.4 第四步:初始化 Spring Web 上下文:容器独立启动

private WebApplicationContext createSpringWebApplicationContext() {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();context.register(SpringWebConfig.class);context.refresh();return context;
}
底层原理:
  • Spring 与 Tomcat 的 “解耦启动”

    这是嵌入式场景的关键设计 ——Spring 容器的初始化不依赖 Tomcat。我们先通过AnnotationConfigWebApplicationContext(注解驱动的 Spring Web 容器)注册配置类SpringWebConfig,再调用refresh()触发 Spring 的生命周期(Bean 扫描、创建、依赖注入)。这种设计的优势是:Spring 容器可以独立调试,且 Tomcat 只需负责 “托管” 已初始化好的 Spring 组件。

  • SpringWebConfig 的作用:该类是 Spring MVC 的核心配置,定义了DispatcherServletRequestMappingHandlerAdapter等关键 Bean,为后续请求处理铺路。

2.5 第五步:动态注册 Servlet:Servlet 3.0 规范的实践

tomcatContext.addServletContainerInitializer((c, servletContext) -> {// 注册原生ServletservletContext.addServlet("helloServlet", new HelloServlet()).addMapping("/hello-servlet");// 注册Spring的DispatcherServletspringContext.getBeansOfType(ServletRegistrationBean.class).values().forEach(registrationBean -> {registrationBean.onStartup(servletContext);});
}, Collections.emptySet());
底层原理:
  • Servlet 3.0 的革命性特性:无 web.xml 配置
    • 传统 Tomcat 通过WEB-INF/web.xml注册 Servlet;而 Servlet 3.0 引入ServletContainerInitializer(SCI),允许通过代码动态注册 Servlet、Filter、Listener。Tomcat 启动Context时,会自动调用 SCI 的onStartup方法,完成动态配置。
  • 两类 Servlet 的注册逻辑
    1. 原生 Servlet(HelloServlet):直接通过ServletContext.addServlet创建实例并映射到/hello-servlet,完全遵循 Servlet API;
    2. Spring 的 DispatcherServlet:通过ServletRegistrationBean间接注册。SpringWebConfig中定义的DispatcherServletRegistrationBean会将DispatcherServlet映射到/(所有请求的入口),onStartup方法本质是调用ServletContext.addServlet完成注册。

2.6 第六步:启动 Tomcat 与主线程保活

tomcatServer.start();
tomcatServer.getServer().await();
底层原理:
  • Tomcat 启动的底层流程tomcatServer.start()会触发 Tomcat 的生命周期链条:init()start(),依次初始化ServerServiceConnectorContext,最终启动Connector的监听线程(开始接收请求)。

  • await ():为何能保活主线程?

    await()底层通过CountDownLatch或 “阻塞等待关闭信号” 实现:主线程会阻塞在await()方法中,直到收到关闭信号(如 Ctrl+C)。若没有这行代码,main方法执行完毕后 JVM 会退出,Tomcat 也会随之关闭。

三、Spring MVC 与 Tomcat 集成的核心:请求从 Tomcat 到 Controller 的链路

当 Tomcat 启动后,请求如何从客户端到达 Spring 的MySpringController?这背后是DispatcherServlet与 Tomcat 的深度联动。

3.1 DispatcherServlet:Spring MVC 的 “前端控制器”

SpringWebConfig中注册的DispatcherServlet是整个链路的核心,它的作用是:

  1. 接收所有请求:因映射路径为/,所有请求(如/hello-spring)都会先进入DispatcherServlet
  2. 请求分发:通过HandlerMapping(默认是RequestMappingHandlerMapping)找到匹配@GetMapping("/hello-spring")MySpringController
  3. 执行 Controller 方法:调用MySpringController.helloSpring(),得到返回的Map
  4. 响应处理:通过HandlerAdapterRequestMappingHandlerAdapter)中的MappingJackson2HttpMessageConverter,将Map转为 JSON 格式的响应体。

3.2 注解驱动的底层:@RestController 与 @GetMapping 如何生效?

  • @RestController:是@Controller+@ResponseBody的组合。@Controller告诉 Spring 这是一个 “请求处理 Bean”,@ResponseBody告诉HandlerAdapter:方法返回值直接作为响应体,无需解析为视图(如 JSP)。
  • @GetMapping:本质是@RequestMapping(method = RequestMethod.GET)RequestMappingHandlerMapping会扫描所有带@RequestMapping的方法,建立 “请求路径 - Controller 方法” 的映射关系,供DispatcherServlet分发使用。

3.3 消息转换:从 Map 到 JSON 的 “魔法”

SpringWebConfig中定义的MappingJackson2HttpMessageConverter是关键:

  • 它实现了HttpMessageConverter接口,负责 “请求体→Java 对象” 和 “Java 对象→响应体” 的转换;
  • MySpringController返回Map时,HandlerAdapter会调用该转换器,使用 Jackson 库将Map序列化为 JSON 字符串,最终通过 Tomcat 的响应流返回给客户端。

四、Servlet 底层机制:生命周期与设计模式的实践

HelloServlet类演示了 Servlet 的底层特性,结合代码日志拆解其核心机制。

4.1 Servlet 生命周期:从创建到销毁的三阶段

Servlet 的生命周期由 Tomcat(Servlet 容器)全权管理,对应HelloServlet中的三个关键方法:

  1. 实例化(构造函数)

    当第一次请求/hello-servlet时,Tomcat 创建HelloServlet实例(仅创建一次),日志打印【Servlet 生命周期】1. 构造函数被调用。

  2. 初始化(init ())

    实例创建后,Tomcat 调用init()方法,完成资源初始化(如加载配置),日志打印【Servlet 生命周期】2. init() 方法被调用(仅调用一次)。

  3. 请求处理(service ()→doGet ())

    每次请求到来时,Tomcat 从线程池分配一个线程,调用service()方法(Servlet 的模板方法),service()根据请求方法(GET)分发到doGet(),日志打印【处理请求】接收到一个GET请求(多次调用,每次请求一个线程)。

  4. 销毁(destroy ())

    当 Tomcat 关闭时,调用destroy()方法释放资源,日志打印【Servlet 生命周期】3.destroy() 方法被调用(仅调用一次)。

4.2 设计模式:Servlet 的模板方法模式

HttpServlet采用模板方法模式设计:

  • 模板方法service(HttpServletRequest, HttpServletResponse)方法定义了请求处理的 “骨架”—— 解析请求方法(GET/POST/PUT 等),然后调用对应的doXXX()方法;
  • 具体实现:开发者无需重写service(),只需根据业务重写doGet()doPost()等方法,降低了开发复杂度。

4.3 线程模型:单实例多线程的利弊

  • 单实例多线程:Tomcat 仅创建一个HelloServlet实例,所有请求由不同线程处理(线程池管理)。这种设计的优势是节省内存,但需注意线程安全—— 若HelloServlet有成员变量(如private int count;),多线程并发修改会导致数据不一致,需通过synchronized等方式保证安全。

五、嵌入式 Tomcat 的本质:从 “容器托管” 到 “代码驱动”

对比传统 Tomcat 部署与嵌入式 Tomcat,我们能更清晰地看到其本质差异:

维度传统 Tomcat 部署嵌入式 Tomcat(本文代码)
部署方式WAR 包放入webapps目录代码创建 Tomcat 实例,无 WAR 包
配置方式server.xmlweb.xml配置Java 代码配置(API 调用)
生命周期控制Tomcat 启动时自动加载应用代码调用start()/stop()控制
灵活性固定目录结构,修改需重启 Tomcat动态调整端口、协议、应用路径
适用场景多应用部署、传统 Web 项目微服务(Spring Boot)、独立应用

Spring Boot 的内嵌 Tomcat,本质就是对本文代码逻辑的 “封装与自动化”—— 它通过TomcatServletWebServerFactory自动创建 Tomcat 实例、配置 Connector、注册 DispatcherServlet,开发者无需编写繁琐的底层代码,只需关注业务逻辑。

六、总结:打通从代码到运行的认知链路

通过本文的拆解,你应该已经理解:嵌入式 Tomcat 并非 “黑魔法”,而是通过 Java 代码调用 Tomcat API,手动构建ServerConnectorContext等核心组件,并结合 Servlet 3.0 规范动态注册 Servlet,最终实现 Web 应用的启动与运行。

整个流程的核心链路可概括为:

创建Tomcat实例 → 配置Connector(请求入口) → 构建Context(应用容器) → 初始化Spring上下文(Bean创建) → 动态注册Servlet(原生+Dispatcher) → 启动Tomcat → 接收请求 → DispatcherServlet分发 → Controller处理 → 响应返回

理解这一底层逻辑,不仅能帮你快速定位 Spring Boot 内嵌服务器的问题(如端口冲突、协议配置异常),更能让你看透 “框架封装” 背后的本质,成为真正懂原理的开发者。

http://www.dtcms.com/a/442188.html

相关文章:

  • MyBatis 进阶
  • 软件设计师-软件工程-软件过程模型
  • 论坛网站方案手机网站建设的趋势
  • LeetCode每日一题——单调数列
  • LeetCode 100题(10题)
  • 后端开发网站做一些什么建设部网站官网 造价鉴定
  • day52-Zabbix(第一部分)
  • 依托Java和百度地图实现长沙市热门道路与景点实时路况检索的实践探索
  • 7-1-查询练习
  • Numpy 手搓线性回归
  • 昆明网站服务器湖北seo推广
  • 医院网站建设怎么样盐城网站建设效果
  • dockerfile理解
  • SpringBoot集成Druid连接池_配置优化与监控实践指南
  • 12380网站建设打算公众号小程序开发公司
  • 高并发场景下的前后端数据同步策略:长轮询、SSE与WebSocket对比分析
  • 网站推广对接北京手机网站搭建费用
  • Raydium
  • 动态Vault
  • 量化交易策略中ATR与波动率的配合
  • 便宜的网站设计企业永久免费自动建站系统
  • 深入解析JS事件循环机制 (Event Loop)
  • 亭湖区建设局网站小红书推广计划
  • 吃透大数据算法-时间轮(TimingWheel)
  • 从输入URL到展示出页面的这个过程~
  • WebDAV 与 SMB 在钓鱼攻击中的区别
  • 8. Pandas 日期与时间序列数据处理
  • 免费网站模板做零食的网站有哪些
  • 从零开始的C++学习生活 2:类和对象(上)
  • 家纺营销型网站网站建设服务费怎么记账