2621 words
13 minutes
Tomcat 工作机制

Tomcat 工作机制#

Tomcat 是什么:HTTP 服务器与 Servlet 容器的组合#

Tomcat 是一个开源的、由 Java 实现的 HTTP 服务器 + Servlet 容器。它对外承担两件事:一是作为 HTTP 服务器监听端口、处理 TCP 连接与 HTTP 协议;二是作为 Servlet 容器,把解析后的请求按规则路由到具体的 Servlet 并管理其生命周期。

一句话概括:Connector 处理 I/O,Catalina 负责路由。 Connector 负责把字节流变成结构化的 Request/Response 对象,Catalina(Tomcat 的 Servlet 容器实现)负责把这些对象分发给正确的业务代码。

为什么存在:Servlet 容器接管了”通信”这件脏活#

在没有 Servlet 容器之前,用 Java 写 Web 应用意味着自己处理 socket、解析 HTTP 报文头、维护连接池——这些代码和业务逻辑毫无关系却极其繁琐。Servlet 容器存在的根本动机是:让 Web 开发者专注于业务逻辑,由容器处理一切与网络通信相关的细节。

一个合格的 Servlet 容器通常承担以下职责:

  • 网络通信:监听端口、接收 TCP 连接、解析 HTTP 请求报文、序列化 HTTP 响应。
  • 请求分发:根据 URL 与映射规则(web.xml 或注解)把请求路由到对应的 Servlet。
  • 生命周期管理:控制 Servlet 的实例化、初始化、服务、销毁。
  • 线程管理:为每个请求分配工作线程,处理并发。
  • 类加载隔离:让部署在同一容器内的多个 Web 应用互不干扰。
  • 资源管理:会话(Session)、JNDI 命名资源、静态资源等。

容器接手之后,开发者只需要继承 HttpServlet,拿到已经解析好的 HttpServletRequestHttpServletResponse 即可:

Context: 容器把字节流封装成可编程对象,业务代码只关心 request/response。

public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain;charset=utf-8");
resp.getWriter().write("Hello, " + req.getParameter("name"));
}
}

Why this way: 业务方法只依赖 Servlet 规范定义的 API,与具体容器实现解耦——同一个 Servlet 可以跑在 Tomcat、Jetty、Undertow 上。

Key params:

  • HttpServletRequest — 已解析的请求(头、参数、体、Cookie)
  • HttpServletResponse — 容器提供的响应写入通道
  • doGet / doPost — 容器根据 HTTP 方法自动路由

工作原理:从启动到一次请求的完整链路#

组件架构:自上而下的树形结构#

Tomcat 的运行时由一棵组件树构成,根节点是 Server,逐层向下:

Server // 整个 Tomcat 实例(全局唯一)
└── Service // 把多个 Connector 绑定到一个 Engine
├── Connector (NIO/HTTP) // 处理 I/O 与协议解析
└── Engine // 整个 Servlet 引擎
└── Host // 虚拟主机(如 localhost)
└── Context // 一个 Web 应用(对应一个 WAR/目录)
└── Wrapper // 一个 Servlet

Why this way: 自上而下的分层让每一层只关心自己的职责——Engine 负责选虚拟主机,Context 负责选 Web 应用,Wrapper 负责选具体 Servlet。这种层级正好对应一次 HTTP 请求从”到哪台主机”到”调哪个方法”的逐级收敛。

Key params:

  • Connector — 可以有多个(同时监听 HTTP/AJP 等不同协议)
  • Context — 类加载隔离与热部署的基本单位
  • Wrapper — 最内层,直接持有 Servlet 实例

启动流程:解析 server.xml,按 Lifecycle 逐层启动#

Context: Tomcat 启动时需要把这棵组件树从配置文件构建出来,再自上而下激活。

Terminal window
# 入口:Catalina 提供的引导类(startup.sh 实际执行的命令)
java org.apache.catalina.startup.Bootstrap start

启动分为两步:先用 Digester(基于 SAX 的 XML→Java 对象映射工具)把 server.xml 解析成上述组件树,然后通过 Lifecycle 接口逐层调用 start()

Why this way: 每个组件都实现 org.apache.catalina.Lifecycle,暴露统一的状态机(NEW → INITIALIZING → INITIALIZED → STARTING → STARTED …)和事件通知机制。父组件启动时会自动触发子组件启动,从而用一套递归逻辑完成整棵树的初始化。

Key params:

  • server.xml — 声明组件树的配置文件(位于 conf/ 目录下)
  • Lifecycle — 组件生命周期的统一接口,支持事件监听
  • Digester — 把 XML 元素映射成 Java 对象与方法的解析器

请求处理:Pipeline 与 Valve 组成的责任链#

一次 HTTP 请求在组件树中的流转路径如下:

Connector(NIO) 接收 TCP 连接、解析 HTTP
│ 构造 Request / Response 对象
Engine Pipeline ──► Host Pipeline ──► Context Pipeline ──► Wrapper Pipeline
(选 Host) (选 Context) (选 Wrapper) (调 Servlet.service())

每个容器(Engine/Host/Context/Wrapper)内部都有一条 Pipeline(管道),由若干 Valve(阀门) 串联而成,链的末尾是该容器的基础 Valve。请求从最外层进入、逐层向下,每一层先经过自定义 Valve(可做日志、鉴权、限流等切面),再由基础 Valve 决定下一层的路由目标:

  • StandardEngineValve → 根据主机名选出 Host
  • StandardHostValve → 根据应用路径选出 Context
  • StandardContextValve → 根据映射规则选出 Wrapper
  • StandardWrapperValve → 加载并调用 Servlet 的 service()

Why this way: Pipeline/Valve 本质是一条责任链,把”路由”与”切面增强”解耦。新增鉴权、日志、访问控制不需要改动核心分发逻辑,只需往某层 Pipeline 里插入一个 Valve。

Key params:

  • Pipeline — 每个容器持有一条阀门链
  • Valve — 链上的处理节点,可自定义
  • StandardWrapperValve — 最终调用 Servlet 的阀门

线程模型:NIO 下的 Acceptor / Poller / Worker#

Tomcat 默认使用一个 java.util.concurrent.ThreadPoolExecutor,最大线程数默认 200。NIO 模式下,线程被分为三种角色,符合 Reactor 模式:

Acceptor ──accept()──► 注册到 Selector ──► Poller ──I/O 就绪──► Worker 线程池
(接收连接) (监听读事件) (检测) (执行 Servlet)
  • Acceptor:阻塞调用 ServerSocket.accept(),拿到新的 Socket 连接后把它交给 Poller。
  • Poller:维护一个 Selector,轮询哪些连接的数据已就绪可读。少量 Poller 线程就能管理大量连接。
  • Worker:从线程池取出工作线程,执行真正的 Servlet 业务逻辑。

Why this way: I/O 等待(数据还没到达)不占用工作线程。Poller 用 Selector 的事件驱动机制,让少数线程监管海量连接;只有当数据真正就绪、需要执行业务时才消耗宝贵的 Worker 线程。这与传统 BIO 模式”一连接一线程”相比,大幅提升了并发承载能力。

Key params:

  • maxThreads(默认 200)— Worker 线程池上限
  • acceptCount — 连接已满时的等待队列长度
  • Selector — NIO 多路复用器,Poller 监听就绪事件的核心

类加载机制:打破双亲委派以实现隔离与热部署#

Tomcat 的类加载器层次与标准 JDK 略有不同:

Bootstrap ClassLoader // JVM 内置
└── System (AppClassLoader) // 加载启动类、catalina.properties
└── Common // lib/ 下,所有应用共享
├── Catalina // Tomcat 自身的类
└── Webapp // 每个 Context 独立:WEB-INF/classes + WEB-INF/lib

关键差异在于 Webapp 类加载器会先尝试自己加载,再委托父加载器(JDK 核心类除外),这与标准双亲委派恰好相反。

Why this way: 先本地加载保证了不同 Web 应用之间的类互相隔离——A 应用的 commons-xxx.jar 与 B 应用即使版本不同也能共存。而热部署之所以可行,正是因为一个 Context 的全部类都由它自己的 Webapp 类加载器持有;重新加载应用时只需丢弃旧加载器、创建新的,旧类随之被 GC,从而在不重启整个 Tomcat 的情况下更新应用。

Key params:

  • Commonlib/ 目录,全局共享库
  • Webapp — 每个 Web 应用独立,是隔离与热部署的单位
  • 双亲委派”反转” — 先本地后父类,实现应用隔离

Servlet 生命周期:单实例多线程#

Servlet 在容器中的生命周期由 Context 驱动:

Context 启动扫描配置(web.xml / 注解)注册 Servlet 定义
→ 首次请求到达时懒加载实例化
→ init(ServletConfig) // 初始化一次
→ service() (并发) // 每个请求一个线程
→ destroy() // Context 卸载时

Why this way: 默认懒加载是为了加快启动速度——只有被请求到的 Servlet 才真正创建。init() 只在实例创建后调用一次,之后所有请求共享同一个实例、由多个工作线程并发调用 service()。这正是 Servlet 编程的核心约束:不要用实例变量保存请求级状态,否则会出现线程安全问题。

Key params:

  • load-on-startup — 大于 0 时改为启动即加载(而非懒加载)
  • init() — 仅执行一次的初始化钩子
  • service() — 每请求一线程,按方法分发到 doGet / doPost

与替代方案的差异:Servlet 兼容性与版本断点#

“Servlet 兼容性”指同一个 Web 应用能否在不同版本、不同品牌的 Servlet 容器上正常运行。它本质上由 Servlet 规范版本 决定。不同版本的 Tomcat 对应不同的规范版本和最低 Java 版本:

Servlet 4.0 → Tomcat 9.x → Java 8+ → 包名 javax.servlet
Servlet 5.0 → Tomcat 10.0 → Java 8+ → 包名 jakarta.servlet
Servlet 6.0 → Tomcat 10.1 → Java 11+ → 包名 jakarta.servlet
Servlet 6.1 → Tomcat 11.0 → Java 21+ → 包名 jakarta.servlet

Why this way: 这里最关键的断点是 Tomcat 9 → 10。Servlet 规范从 Java EE 迁移到 Jakarta EE(由 Eclipse 基金会接管)时,因为 Oracle 不允许继续使用 javax 命名空间,API 包名被强制从 javax.servlet 改为 jakarta.servlet

这意味着为 Tomcat 9 编译的 WAR 无法直接运行在 Tomcat 10 上——所有 import javax.servlet.* 都会失败,必须重新编译,或借助迁移工具批量改写 import。

与其他容器的横向对比:

  • Jetty:同样是 Servlet 容器,更轻量,嵌入式场景友好。
  • Undertow:性能导向,是 WildFly 的默认 Web 服务器。
  • WildFly / GlassFish / Liberty:完整的 Jakarta EE 应用服务器,除 Servlet 外还提供 EJB、JPA、JMS 等全栈能力。

如果只需要 Servlet 容器能力,选 Tomcat 即可;需要完整 Jakarta EE 全栈时才考虑后者。

何时选择 Tomcat#

  • 运行基于 Servlet 规范的 Java Web 应用 → Tomcat 是首选。
  • 只需 Servlet 容器、不需要完整 Jakarta EE 全栈 → 选 Tomcat 而非 WildFly/GlassFly,更轻量。
  • 微服务 / 嵌入式场景 → Spring Boot 默认内嵌 Tomcat,开箱即用。
  • 高并发静态资源 / 反向代理 → 前面挂一层 Nginx 处理静态与负载均衡,Tomcat 只负责动态请求。

理解了”Connector 处理 I/O、Catalina 负责路由”这一主线,再回看启动流程、请求链路、线程模型与类加载,就都能串成一条完整的因果链。

Tomcat 工作机制
https://sgjki547.top/posts/tomcat-工作机制/
Author
SGJki
Published at
2026-06-15
License
CC BY-NC-SA 4.0