本文整合 Tomcat 从启动、请求接收、路由分发,到 Spring MVC 集成、多应用部署、核心组件(Pipeline/Valve/Servlet)的全部核心原理,结合具体源码、配置文件和实战示例,彻底打通 Tomcat 与 Spring MVC 的底层链路,解决新手常见的所有核心疑问(如:请求怎么到应用?DispatcherServlet 怎么被找到?多域名/多端口怎么配?Pipeline 是什么?)。
全程以「实战+源码」为核心,不绕抽象概念,所有内容均对应 Tomcat 真实运行逻辑,可直接对照源码调试、对照配置实操。
一、Tomcat 启动流程原理(详细源码+流程)
Tomcat 启动的核心是「初始化核心组件」,从 Server → Service → Connector → Engine → Host → Context → Wrapper,逐层初始化,最终完成端口监听、组件关联,为接收请求做好准备。
1.1 核心组件层级(必记)
Tomcat 组件层级关系(从上到下,父组件包含子组件):
xml
<Server> <!-- 整个Tomcat实例,唯一 -->
<Service> <!-- 服务,绑定Connector和Engine -->
<Connector/> <!-- 监听端口,接收请求 -->
<Engine> <!-- 引擎,路由请求到Host -->
<Host> <!-- 虚拟主机,对应域名 -->
<Context/><!-- 应用上下文,对应一个Web应用 -->
<Wrapper/><!-- Servlet包装器,对应一个Servlet -->
</Context>
</Host>
</Engine>
</Service>
</Server>
1.2 启动核心流程(带源码)
Tomcat 启动入口类:org.apache.catalina.startup.Tomcat,核心流程分 3 步:初始化组件 → 启动组件 → 监听端口。
第一步:初始化 Server
Server 是 Tomcat 顶层组件,负责管理 Service,初始化时创建 Service 并关联。
java
// Tomcat 启动核心代码(简化版,与源码结构一致)
public class Tomcat {
public void start() throws LifecycleException {
// 1. 初始化 Server(默认实现:StandardServer)
Server server = getServer();
server.init(); // 初始化Server,内部会初始化Service
// 2. 启动 Server(会触发所有子组件启动)
server.start();
// 3. 监听关闭信号(如Ctrl+C)
await();
}
}
第二步:初始化 Service
Service 负责绑定 Connector 和 Engine,一个 Service 可以有多个 Connector(多端口),但只有一个 Engine。
java
// StandardService 初始化源码(简化)
public class StandardService extends LifecycleBase {
private Connector[] connectors = new Connector[0];
private Engine engine;
@Override
protected void initInternal() {
// 初始化 Engine
if (engine != null) {
engine.init();
}
// 初始化所有 Connector
for (Connector connector : connectors) {
connector.init();
}
}
@Override
protected void startInternal() {
// 启动 Engine
if (engine != null) {
engine.start();
}
// 启动所有 Connector(监听端口)
for (Connector connector : connectors) {
connector.start();
}
}
}
第三步:初始化 Connector(核心:监听端口)
Connector 是 Tomcat 接收请求的入口,核心组件:Endpoint(监听端口)、Processor(解析HTTP)、Adapter(桥接Engine)。
java
// Connector 初始化核心逻辑(简化)
public class Connector extends LifecycleBase {
private Endpoint endpoint; // 默认:NioEndpoint(NIO模式)
private Adapter adapter; // 默认:CoyoteAdapter
@Override
protected void initInternal() {
// 初始化 Endpoint(绑定端口)
endpoint.init();
// 初始化 Adapter(桥接 Engine)
adapter = new CoyoteAdapter(this);
}
@Override
protected void startInternal() {
// 启动 Endpoint,开始监听端口(如8080)
endpoint.start();
}
}
Endpoint 启动后,会创建 ServerSocket 监听指定端口(如8080),接收客户端TCP连接,交给 Processor 解析HTTP报文。
第四步:初始化 Engine/Host/Context/Wrapper
Engine、Host、Context、Wrapper 均为「容器组件」,遵循 Lifecycle 接口(初始化init()、启动start()),逐层初始化:
-
Engine:初始化时关联所有 Host,设置默认Host(如localhost);
-
Host:初始化时关联所有 Context,设置应用目录(appBase);
-
Context:初始化时加载应用的 web.xml,创建 Wrapper(对应Servlet);
-
Wrapper:初始化时反射创建 Servlet 实例(如DispatcherServlet),调用 Servlet.init()。
1.3 启动总结(一句话)
Tomcat 启动 = 逐层初始化 Server→Service→Connector→Engine→Host→Context→Wrapper,最终启动 Connector 监听端口,初始化所有 Servlet,完成请求接收准备。
二、请求从浏览器到 Tomcat 的完整流转(源码+链路)
核心疑问:浏览器请求进来,Tomcat 是怎么接收、解析、路由,最终交给应用的 DispatcherServlet 的?全程带源码、带链路。
2.1 实战示例(基础配置)
假设:Tomcat 监听8080端口,部署一个 Spring MVC 应用(app1),请求 URL:http://localhost:8080/app1/demo/hello
应用 web.xml 配置(Spring MVC 核心):
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!-- 配置 DispatcherServlet(Spring MVC 唯一核心Servlet) -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup> <!-- 启动时初始化 -->
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern> <!-- 匹配应用内所有请求 -->
</servlet-mapping>
</web-app>
2.2 完整请求流转链路(带源码)
链路总览:浏览器 → TCP连接 → Connector → Engine → Host → Context → Wrapper → DispatcherServlet → Controller
第一步:浏览器发起请求,Connector 接收
-
浏览器与 Tomcat 建立 TCP 三次握手,发送 HTTP 请求(如 GET /app1/demo/hello);
-
Connector 的 Endpoint(NioEndpoint)接收 Socket 连接,封装成
SocketProcessor扔给线程池; -
Processor(Http11Processor)解析 HTTP 报文,生成 Tomcat 内部请求/响应对象:
java
// Http11Processor 解析逻辑(简化)
public class Http11Processor {
public void service(SocketWrapperBase<?> socket) {
// 读取HTTP请求流,解析请求行、请求头、请求体
Request coyoteRequest = new Request();
Response coyoteResponse = new Response();
parseRequest(socket, coyoteRequest, coyoteResponse);
// 交给 Adapter 桥接 Engine
adapter.service(coyoteRequest, coyoteResponse);
}
}
第二步:Connector 通过 CoyoteAdapter 交给 Engine
这是「请求从 Connector 到 Engine」的关键一步,CoyoteAdapter 负责将 Tomcat 内部请求,转成 Servlet 规范的 HttpServletRequest/HttpServletResponse,并调用 Engine 的 Pipeline。
java
// CoyoteAdapter.service() 核心源码(简化,真实逻辑一致)
public class CoyoteAdapter {
private final Connector connector;
public void service(Request req, Response res) {
// 1. 将 Tomcat 内部请求,转成 Servlet 规范对象
org.apache.catalina.connector.Request request =
(org.apache.catalina.connector.Request) req.getNote(ADAPTER_NOTES);
org.apache.catalina.connector.Response response =
(org.apache.catalina.connector.Response) res.getNote(ADAPTER_NOTES);
// 2. 拿到当前 Service 关联的 Engine
Service service = connector.getService();
Engine engine = service.getContainer();
// 3. 🔥 核心:将请求交给 Engine 的 Pipeline(交付请求)
engine.getPipeline().getFirst().invoke(request, response);
}
}
第三步:Engine → Host → Context → Wrapper 路由(Pipeline+Valve 责任链)
Tomcat 所有容器(Engine/Host/Context/Wrapper)都有 Pipeline(管道),Pipeline 是一条 Valve(阀门)责任链,请求通过 Valve 依次传递,最终到达 Wrapper。
3.1 什么是 Pipeline 和 Valve?(核心解答)
-
Pipeline:容器的「请求处理流水线」,内部维护一串 Valve,请求从第一个 Valve 开始,依次传递到最后一个 Valve(BasicValve),每个 Valve 负责一件具体事情(如日志、安全检查、路由);
-
Valve :流水线的「工人」,接口只有一个
invoke()方法,负责处理请求,并调用下一个 Valve。
java
// Valve 核心接口
public interface Valve {
void invoke(Request request, Response response);
}
// Pipeline 核心接口
public interface Pipeline {
Valve getFirst(); // 获取流水线第一个 Valve
void setBasic(Valve valve); // 设置最后一个 Valve(核心逻辑)
}
3.2 路由链路(每层 Pipeline 调用逻辑)
请求从 Engine 的第一个 Valve 开始,逐层向下传递,每一层 Valve 完成自己的逻辑后,调用下一层容器的 Pipeline。
- Engine 层:
java
// StandardEngineValve.invoke() 源码(简化)
public class StandardEngineValve extends ValveBase {
@Override
public void invoke(Request request, Response response) {
// 1. 核心逻辑:根据请求头 Host,找到对应的 Host(如localhost)
Host host = (Host) getContainer().findChild(request.getHost());
// 2. 调用 Host 的 Pipeline(传递请求)
host.getPipeline().getFirst().invoke(request, response);
}
}`
- Host 层:
java
// StandardHostValve.invoke() 源码(简化)
public class StandardHostValve extends ValveBase {
@Override
public void invoke(Request request, Response response) {
// 1. 核心逻辑:根据请求 URI 前缀,找到对应的 Context(应用)
String contextPath = request.getContextPath(); // 如 /app1
Context context = (Context) getContainer().findChild(contextPath);
// 2. 调用 Context 的 Pipeline(传递请求)
context.getPipeline().getFirst().invoke(request, response);
}
}`
- Context 层:
java
// StandardContextValve.invoke() 源码(简化)
public class StandardContextValve extends ValveBase {
@Override
public void invoke(Request request, Response response) {
// 1. 核心逻辑:根据请求 URI,匹配对应的 Wrapper(Servlet)
Mapper mapper = request.getMapper();
Mapper.MapResult mapping = mapper.map(request.getHost(), request.getRequestURI());
Wrapper wrapper = mapping.wrapper; // 匹配到 DispatcherServlet 对应的 Wrapper
// 2. 调用 Wrapper 的 Pipeline(传递请求)
wrapper.getPipeline().getFirst().invoke(request, response);
}
}`
4. **Wrapper 层**:
```java
// StandardWrapperValve.invoke() 源码(简化,最关键一步)
public class StandardWrapperValve extends ValveBase {
@Override
public void invoke(Request request, Response response) {
// 1. 核心逻辑:从 Wrapper 中获取 Servlet 实例(DispatcherServlet)
Servlet servlet = wrapper.allocate(); // 反射创建/获取 Servlet 单例
// 2. 🔥 调用 Servlet 的 service() 方法(进入 Spring MVC)
servlet.service(request.getRequest(), response.getResponse());
}
}`
第四步:调用 DispatcherServlet,进入 Spring MVC
Wrapper 中获取的 Servlet 实例,就是 Spring MVC 配置的 DispatcherServlet(唯一核心 Servlet),调用 servlet.service() 后,请求正式进入 Spring MVC 框架,后续由 DispatcherServlet 分发到具体的 Controller。
java
// DispatcherServlet.service() 核心逻辑(简化)
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) {
// 1. 将 Spring Web 容器绑定到请求
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
// 2. 分发请求到 Controller
doDispatch(request, response);
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
// 匹配 Controller 方法、执行拦截器、调用 Controller、处理返回值
HandlerExecutionChain mappedHandler = getHandler(request); // 找 Controller
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
ha.handle(request, response, mappedHandler.getHandler()); // 执行 Controller
}
}
2.3 流转总结
请求流转 = 接收(Connector)→ 桥接(CoyoteAdapter)→ 路由(Engine→Host→Context→Wrapper,Pipeline+Valve 责任链)→ 调用(DispatcherServlet)→ 业务处理(Controller)。
三、核心疑问解答(源码+实例)
整合前面所有疑问,逐一解答,结合源码和配置,彻底消除困惑。
疑问1:DispatcherServlet 是在 web.xml 配置的吗?web.xml 是每个应用一个还是 Tomcat 共用?
答:是在 web.xml 配置(Spring Boot 自动配置,本质一致);web.xml 是每个应用独有一个,不是 Tomcat 共用。
-
Tomcat 结构:每个应用对应一个 Context,每个 Context 下都有
WEB-INF/web.xml,只负责当前应用的 Servlet、过滤器、监听器配置; -
示例:部署 2 个应用(app1、app2),每个应用都有自己的 web.xml,各自配置自己的 DispatcherServlet,互相隔离;
-
Tomcat 全局有一个
conf/web.xml(全局配置),但仅配置默认 Servlet(如 DefaultServlet)、MIME 类型等,不负责应用的 Servlet 配置。
疑问2:Tomcat 中不同应用怎么配置?怎么区分不同应用的请求?
答:通过 server.xml 配置多个 Context(一个 Context 对应一个应用),通过 Context Path(路径前缀) 区分请求。
实战配置(server.xml)
xml
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!-- 应用1:path=/app1,对应 webapps/app1 目录 -->
<Context path="/app1" docBase="app1" reloadable="true"/>
<!-- 应用2:path=/app2,对应 webapps/app2 目录 -->
<Context path="/app2" docBase="app2" reloadable="true"/>
<!-- 根应用:path="",对应 webapps/ROOT 目录 -->
<Context path="" docBase="ROOT" reloadable="true"/>
</Host>
</Engine>
</Service>
</Server>
请求区分规则
-
http://localhost:8080/app1/xxx→ 匹配 path=/app1 → 应用1; -
http://localhost:8080/app2/xxx→ 匹配 path=/app2 → 应用2; -
http://localhost:8080/xxx→ 匹配 path="" → 根应用。
关键:不同应用的 Context 互相隔离,拥有自己的 ClassLoader、Servlet、Spring 容器,互不影响。
疑问3:不同域名、不同端口怎么配置?
答:不同端口 = 多 Connector;不同域名 = 多 Host,可自由组合,配置如下。
实战配置(多端口+多域名)
xml
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<!-- 端口8080 -->
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>
<!-- 端口8081 -->
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>
<Engine name="Catalina" defaultHost="localhost">
<!-- 域名1:localhost(默认),对应 webapps 目录 -->
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"/>
<!-- 域名2:www.a.com,对应 webapps_a 目录 -->
<Host name="www.a.com" appBase="webapps_a" unpackWARs="true" autoDeploy="true"/>
<!-- 域名3:www.b.com,对应 webapps_b 目录 -->
<Host name="www.b.com" appBase="webapps_b" unpackWARs="true" autoDeploy="true"/>
</Engine>
</Service>
</Server>
路由效果
-
http://localhost:8080/app1/xxx→ 端口8080 + 域名localhost + 应用app1; -
http://www.a.com:8080/xxx→ 端口8080 + 域名www.a.com + webapps_a 下的应用; -
http://www.b.com:8081/xxx→ 端口8081 + 域名www.b.com + webapps_b 下的应用。
疑问4:Servlet 是一个执行链吗?Wrapper 找到的 Servlet 是 DispatcherServlet 吗?
答:Servlet 本身不是执行链,但 Tomcat 调用 Servlet 的过程是一条 Pipeline+Valve 执行链;Wrapper 找到的 Servlet 就是应用的 DispatcherServlet。
-
Servlet 本质:一个接口(
javax.servlet.Servlet),核心方法service(req, res),是请求处理的「终点」; -
执行链来源:Tomcat 的 Pipeline+Valve 责任链,请求经过 Engine→Host→Context→Wrapper 的 Valve 链,最终到 WrapperValve,才调用 Servlet;
-
Wrapper 与 DispatcherServlet 的关系:每个 Servlet 对应一个 Wrapper,应用 web.xml 配置的 DispatcherServlet,会被 Tomcat 封装成一个 StandardWrapper,请求匹配到对应的 Wrapper 后,通过
wrapper.allocate()获取 DispatcherServlet 实例,调用其service()方法。
疑问5:为什么 Engine.getPipeline().getFirst() 是一个管道?拿到的是什么?
答:Tomcat 用 Pipeline+Valve 责任链模式,实现请求处理的「插件式扩展」,getFirst() 拿到的是当前 Pipeline 的第一个 Valve。
-
为什么用管道(Pipeline):避免代码耦合,每个 Valve 负责一件事(如日志、安全、路由),可自由添加/删除 Valve,无需修改核心逻辑;
-
getFirst() 拿到的是什么:以 Engine 为例,getFirst() 拿到的是
StandardEngineValve(Engine 流水线的第一个 Valve),调用其invoke()方法,会触发整个 Valve 链的执行,最终完成路由到 Host 的逻辑; -
每层容器的 Pipeline 第一个 Valve:Engine→StandardEngineValve、Host→StandardHostValve、Context→StandardContextValve、Wrapper→StandardWrapperValve,依次传递请求。
疑问6:Controller 是 Servlet 吗?
答:绝对不是!
-
Servlet:只有 DispatcherServlet 这一个(Spring MVC 核心),是 Tomcat 能识别的、实现 Servlet 接口的组件;
-
Controller:普通 Java 类,加了
@Controller注解,是 Spring MVC 内部的业务处理器,由 DispatcherServlet 分发调用,Tomcat 完全不知道 Controller 的存在。
四、核心组件&链路总结(必背)
4.1 核心组件关系
text
Server → Service → Connector(多端口) + Engine(单引擎)
Engine → Host(多域名) → Context(多应用) → Wrapper(多Servlet)
Wrapper → Servlet(如DispatcherServlet) → Spring MVC → Controller
4.2 核心链路(请求→响应)
-
Tomcat 启动:逐层初始化组件,Connector 监听端口;
-
浏览器发起请求,Connector 接收并解析 HTTP 报文;
-
CoyoteAdapter 将请求交给 Engine 的 Pipeline;
-
Engine→Host→Context→Wrapper 逐层路由(Valve 链传递);
-
Wrapper 拿出 DispatcherServlet,调用其 service() 方法;
-
DispatcherServlet 分发请求到 Controller,执行业务逻辑;
-
返回结果经 DispatcherServlet→Wrapper→Context→Host→Engine→Connector,响应给浏览器。
4.3 关键结论
-
Tomcat 核心:Pipeline+Valve 责任链(扩展灵活)、Context 隔离(多应用);
-
Spring MVC 与 Tomcat 的桥梁:DispatcherServlet(唯一 Servlet);
-
多端口:多 Connector;多域名:多 Host;多应用:多 Context;
-
请求匹配顺序:端口→域名→Context Path→Servlet URL-Pattern。
本文覆盖 Tomcat 从启动到请求处理、多应用部署的全部核心原理,结合源码、配置、实例,解决所有新手常见疑问,可作为 Tomcat 底层学习的核心笔记,对照源码调试可快速打通底层逻辑。