很多初学者在学 Java Web 时,都会产生一个困惑:我写的 Java 代码明明只是普通的类和方法,它是怎么接收到浏览器发来的请求的?Tomcat 在其中到底扮演了什么角色?
今天这篇文章,就用最直白的语言,把这件事彻底讲清楚。
一、先搞清楚一个前提:Jar 包不能”接电话”
很多人第一次部署项目时,会以为”把代码打成 Jar 包,运行起来,前端就能访问了”。
但真相是:
- Jar 包本质上只是一个压缩包(类似 zip),里面装着编译好的
.class文件、配置文件和资源文件。 - 它没有任何网络监听能力。你直接运行一个纯业务逻辑的 Jar,它执行完
main方法就退出了,根本不知道什么是 HTTP 请求。
打个比方:Jar 包就像一本写好了菜谱的书,但它自己不会做饭,也听不见客人点菜的声音。
那谁来”听”客人的需求,把菜做出来,再端给客人呢?
答案是:Tomcat。
二、Tomcat 的核心角色:协议翻译官
前端(浏览器)只懂 HTTP 协议,Java 业务代码只懂 Java 对象。两者语言不通,无法直接对话。
Tomcat 就是站在它们中间的翻译官 + 接线员。
请求进来时(翻译方向:HTTP → Java)
| 步骤 | 发生了什么 |
|---|---|
| 1 | 浏览器发起 HTTP 请求(包含 URL、方法、参数、Header 等) |
| 2 | Tomcat 的 Connector 组件监听端口(默认 8080),接收到原始 TCP 数据流 |
| 3 | Tomcat 解析这段原始文本,提取出路径、参数、请求头等信息 |
| 4 | 将解析结果封装成标准的 HttpServletRequest 对象 |
| 5 | 根据 URL 路径,找到对应的 Servlet(你写的业务代码),调用其方法 |
响应出去时(翻译方向:Java → HTTP)
| 步骤 | 发生了什么 |
|---|---|
| 1 | 你的 Java 代码处理完业务逻辑,得到一个结果对象(如 List<Order>) |
| 2 | 结果被序列化为 JSON / HTML 等格式 |
| 3 | 写入 HttpServletResponse 对象,设置状态码(200)、Content-Type 等 |
| 4 | Tomcat 将 Response 对象拼接成标准的 HTTP 响应文本流 |
| 5 | 通过网络发回浏览器,前端渲染页面 |
一句话总结:Tomcat 负责把 HTTP 请求”翻译”成 Java 能处理的对象传进业务层,再把业务层的返回值”翻译”成 HTTP 响应送回前端。
三、完整闭环:一个请求的一生
把上面的内容串起来,一个完整的 Web 请求生命周期是这样的:
用户点击按钮
↓
浏览器构建 HTTP 请求,发送到服务器 8080 端口
↓
Tomcat Connector 监听并接收
↓
解析为 HttpServletRequest 对象
↓
根据 URL 路由,找到对应的 Servlet
↓
调用你的 Java 业务代码(查数据库、调接口、做计算...)
↓
业务代码将结果写入 HttpServletResponse
↓
Tomcat 将 Response 序列化为 HTTP 响应报文
↓
浏览器接收响应,渲染页面
↓
用户看到结果
整个过程中,你的 Java 代码只关心业务逻辑(查订单、算价格、校验权限),完全不需要自己去解析 TCP 字节流、拼 HTTP 报文。这些”脏活累活”全部由 Tomcat 代劳。
四、那 Spring Boot 的 java -jar 是怎么回事?
你可能会问:
“不对啊,我用 Spring Boot 打包成 Jar,直接
java -jar app.jar就能启动 Web 服务了,不是说 Jar 不能接请求吗?”
这里有一个”障眼法”:
Spring Boot 把 Tomcat 也打包进了你的 Jar 里。
当你运行 java -jar springboot-app.jar 时,实际发生的事情是:
- Jar 里的
main方法启动; - 内部自动创建并启动了一个嵌入式 Tomcat 实例;
- 将你的 Controller / Servlet 注册到这个 Tomcat 中;
- Tomcat 开始监听端口,等待请求。
所以表面上是”Jar 在接收请求”,本质上依然是 Tomcat 在做网络通信和协议翻译,只不过它被藏在了 Jar 包内部,你感知不到而已。
这也是为什么 Spring Boot 被称为”约定优于配置”——它帮你省去了手动安装、配置、部署 Tomcat 的繁琐步骤,但底层机制从未改变。
五、一个比喻,彻底记住
| 角色 | 比喻 | 职责 |
|---|---|---|
| 前端(浏览器) | 说外语的客人 | 发出需求(HTTP 请求) |
| Tomcat | 翻译官 + 服务员 | 听懂客人的话,转达给后厨;把后厨的菜端给客人 |
| Java 业务代码 | 后厨厨师 | 只做菜(处理业务逻辑),不跟客人直接交流 |
| Jar 包 | 菜谱书 | 记载了怎么做菜,但自己不会动 |
| 数据库 | 食材仓库 | 提供原材料 |
没有翻译官,客人和厨师永远无法沟通。这就是 Tomcat 存在的意义。
六、总结
前端只懂 HTTP 协议,Java 业务代码只懂 Java 对象。Tomcat 是两者之间的”协议翻译官”和”网络连接器”。 它负责监听端口、解析请求、路由分发、封装响应。而 Jar 包本身只是代码的载体,不具备网络通信能力。
理解了这一点,你就真正搞懂了 Java Web 最底层的运行逻辑。后续无论是学习 Spring MVC 的 DispatcherServlet 路由、还是理解 Nginx 反向代理到 Tomcat 的链路,都会水到渠成。
如果这篇文章帮你理清了思路,欢迎点赞收藏。下一篇我们聊聊:Spring MVC 的 DispatcherServlet 是如何在 Tomcat 之上做”二次路由”的。
七、DispatcherServlet 的”二次路由”——Tomcat 翻译完之后,Spring 内部再分一次
你已经理解了 Tomcat 是”翻译官”,那 DispatcherServlet 就是 Tomcat 翻译完之后,Spring MVC 内部的”二次调度员”。
先明确一个关键事实:Tomcat 根本不认识你的 Controller
你的 UserController、OrderController 这些类,虽然加了 @Controller 注解,但它们只是普通的 Java 类,没有实现 HttpServlet 接口,Tomcat 完全不知道它们的存在。
整个项目中,Tomcat 真正认识的、注册在案的 Servlet 只有一个——DispatcherServlet。
Spring 在启动时,把 DispatcherServlet 注册到了 Tomcat 的根路径 / 上,意思是:所有请求,不管什么 URL,统统先交给 DispatcherServlet 处理。
两层路由的分工
这就形成了两层路由机制:
| 层级 | 负责者 | 做什么 |
|---|---|---|
| 第一层路由 | Tomcat | 根据 URL 匹配到具体的 Servlet → 发现是 / → 交给 DispatcherServlet |
| 第二层路由 | DispatcherServlet | 在 Spring 内部再根据 URL 匹配到具体的 Controller 方法 |
打个比方:Tomcat 是大楼前台,所有来访者先到前台。前台只认识一个部门——“调度部”(DispatcherServlet)。来访者到了调度部之后,调度部再根据来访者的具体需求,把他带到对应的工位(Controller 方法)。
DispatcherServlet 内部做了什么?
当 Tomcat 把请求交给 DispatcherServlet 后,它调用的是 doDispatch() 方法,这是整个 Spring MVC 的心脏。
核心流程就五步:
请求进入 doDispatch()
↓
① HandlerMapping:根据 URL 找到对应的 Controller 方法(Handler)
↓
② HandlerAdapter:找到能"执行"这个方法的适配器
↓
③ 执行拦截器链(Interceptor)的 preHandle
↓
④ 通过适配器,反射调用你的 Controller 方法
↓
⑤ 处理返回值(JSON 序列化 / 视图渲染),写回 Response
关键组件解析
HandlerMapping —— “查号台”
Spring 启动时,会扫描所有带 @Controller 或 @RestController 的类,把每个 @RequestMapping 标注的路径和对应的 Java 方法建立映射关系,存成一张”路由表”。
请求进来时,HandlerMapping 就拿着请求的 URL 去这张表里查:
“路径是
/users/123,谁负责处理?” → “是UserController.getUser()方法”
HandlerAdapter —— “执行器”
DispatcherServlet 不直接调用你的方法,而是通过 HandlerAdapter 来执行。这是适配器模式的经典应用——DispatcherServlet 不关心你的处理器到底长什么样(可能是注解方式、可能是接口方式),它只面向 HandlerAdapter 接口编程,由适配器去适配不同的处理器类型。
完整链路一图流
浏览器请求 GET /users/123
↓
【第一层:Tomcat】
Tomcat 接收 HTTP 请求,解析为 HttpServletRequest
↓
Tomcat 路由:URL 匹配到 DispatcherServlet(映射路径 /)
↓
调用 DispatcherServlet.service()
↓
【第二层:Spring MVC】
DispatcherServlet.doDispatch() 开始二次路由
↓
HandlerMapping 查表:/users/123 → UserController.getUser()
↓
HandlerAdapter 执行 UserController.getUser(123)
↓
方法返回 User 对象
↓
HttpMessageConverter 将 User 序列化为 JSON
↓
写入 HttpServletResponse,返回给 Tomcat
↓
Tomcat 将 Response 发回浏览器
↓
用户看到 JSON 数据
一句话总结
Tomcat 做的是”粗路由”:把请求从网络层带到 Java 世界,交给 DispatcherServlet。 DispatcherServlet 做的是”细路由”:在 Spring 内部根据 URL 精确匹配到具体的 Controller 方法,再通过适配器执行。
两者分工明确,各司其职。Tomcat 管”网络协议 → Servlet”,DispatcherServlet 管”Servlet → 业务方法”。
理解了这两层路由,你回头看 Spring Boot 的自动配置、拦截器、异常处理等机制,就都会豁然开朗。