Tomcat 中的 Pipeline 与 Valve:请求处理的责任链模式
Tomcat 的 Pipeline
(管道)和 Valve
(阀门)是其核心组件,用于处理请求和响应的流程控制。它们基于责任链模式设计,将请求处理的不同任务拆解为独立的阀门,通过管道串联执行,实现了请求处理逻辑的解耦与灵活扩展。本文将深入解析 Pipeline 与 Valve 的工作原理、结构及配置方式。
Pipeline 与 Valve 的核心概念
基本定义
- Pipeline(管道):
每个 Tomcat 容器(Engine
、Host
、Context
、Wrapper
)都包含一个 Pipeline,它是一个任务执行链,负责按顺序调用一系列 Valve 处理请求。 - Valve(阀门):
每个 Valve 代表一个具体的处理任务(如日志记录、权限校验、请求转发等),类似于过滤器(Filter),但作用于 Tomcat 容器级别而非 Web 应用级别。 - Basic Valve(基础阀门):
每个 Pipeline 必须包含一个 Basic Valve,作为管道的最终处理器(责任链的终点),负责执行容器的核心逻辑(如Wrapper
的 Servlet 调用、Host
的虚拟主机路由)。
设计思想:责任链模式
Pipeline 与 Valve 的设计借鉴了责任链模式,其核心特点是:
- 每个 Valve 只处理自己负责的任务,处理完成后将请求传递给下一个 Valve。
- 可通过添加 / 移除 Valve 灵活扩展请求处理逻辑,无需修改原有代码。
- 不同容器的 Pipeline 独立工作,形成层级化的处理流程(如 Engine → Host → Context → Wrapper)。
Pipeline 与 Valve 的结构与实现
容器与 Pipeline 的对应关系
Tomcat 的四大容器(从顶层到应用层)均内置 Pipeline:
容器类型 | 对应的 Pipeline | Basic Valve 作用 |
---|---|---|
Engine |
StandardPipeline |
负责将请求路由到对应的 Host 容器。 |
Host |
StandardPipeline |
负责将请求路由到对应的 Context 容器。 |
Context |
StandardPipeline |
负责将请求路由到对应的 Wrapper 容器。 |
Wrapper |
StandardPipeline |
负责加载并调用 Servlet 处理请求。 |
示例:StandardEngine
中 Pipeline 的初始化代码:
1 | public class StandardEngine extends ContainerBase implements Engine { |
Valve 的链式存储与调用
Valve 在 Pipeline 中以单向链表的形式存储,通过 getNext()
和 setNext()
维护顺序:
1 | public class StandardPipeline implements Pipeline { |
调用流程:
当请求进入容器时,Pipeline 从第一个 Valve 开始调用,依次执行每个 Valve 的 invoke()
方法,最终到达 Basic Valve:
1 | // Valve 接口的核心方法 |
请求处理的完整流程(多层 Pipeline 协作)
一个 HTTP 请求在 Tomcat 中的处理需要经过多层容器的 Pipeline,流程如下:
- 请求进入 Engine 容器:
Engine
的 Pipeline 执行一系列 Valve(如AccessLogValve
记录访问日志)。- 最终由
StandardEngineValve
(Basic Valve)根据域名将请求路由到对应的Host
。
- 请求进入 Host 容器:
Host
的 Pipeline 执行 Valve(如SingleSignOn
处理单点登录)。- 由
StandardHostValve
(Basic Valve)根据请求路径路由到对应的Context
。
- 请求进入 Context 容器:
Context
的 Pipeline 执行 Valve(如RemoteAddrValve
限制 IP 访问)。- 由
StandardContextValve
(Basic Valve)根据 Servlet 映射路由到对应的Wrapper
。
- 请求进入 Wrapper 容器:
Wrapper
的 Pipeline 执行 Valve(如RequestDumperValve
打印请求详情)。- 由
StandardWrapperValve
(Basic Valve)加载并调用目标 Servlet 的service()
方法。
示意图:
1 | 请求 → Engine Pipeline [Valve1 → Valve2 → ... → StandardEngineValve] |
Valve 的配置与常用实现
配置 Valve(以 Host 为例)
Valve 可在 server.xml
中对应容器下配置,例如为 Host
添加访问日志 Valve:
1 | <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> |
className
:Valve 的实现类全路径。- 其他属性:Valve 特有的配置(如日志路径、允许的 IP 规则)。
常用 Valve 实现
Tomcat 内置了多种实用 Valve,覆盖日志、安全、性能等场景:
Valve 类名 | 作用说明 |
---|---|
AccessLogValve |
记录访问日志(IP、请求路径、响应码等),支持自定义格式。 |
RemoteAddrValve |
根据客户端 IP 地址允许 / 拒绝访问(如限制内网访问)。 |
RemoteHostValve |
根据客户端主机名允许 / 拒绝访问。 |
SingleSignOn |
实现多 Web 应用间的单点登录(共享认证信息)。 |
RequestDumperValve |
打印请求 / 响应的详细信息(用于调试)。 |
ErrorReportValve |
生成友好的错误页面(如 404、500 错误)。 |
SlowAccessValve |
记录慢请求(超过指定时间的请求),用于性能排查。 |
自定义 Valve 开发
若内置 Valve 无法满足需求,可自定义 Valve 扩展功能,步骤如下:
实现 Valve 接口:
继承ValveBase
(简化实现),重写invoke()
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import javax.servlet.ServletException;
import java.io.IOException;
public class CustomValve extends ValveBase {
public void invoke(Request request, Response response)
throws IOException, ServletException {
// 1. 处理请求前的逻辑(如计时开始)
long start = System.currentTimeMillis();
// 2. 传递给下一个 Valve
if (getNext() != null) {
getNext().invoke(request, response);
}
// 3. 处理响应后的逻辑(如计算耗时)
long end = System.currentTimeMillis();
System.out.println("请求 " + request.getRequestURI() + " 耗时:" + (end - start) + "ms");
}
}编译与部署:
将类打包为 JAR,放入$CATALINA_BASE/lib
目录。配置使用:
在server.xml
中对应容器添加自定义 Valve:1
2
3<Context path="/myapp" docBase="myapp">
<Valve className="com.example.CustomValve" />
</Context>
Pipeline 与 Filter 的区别
尽管 Valve 和 Filter 都用于请求处理,但二者存在本质区别:
对比维度 | Valve | Filter |
---|---|---|
作用范围 | Tomcat 容器级别(Engine/Host 等) | Web 应用级别(仅当前应用) |
配置位置 | server.xml 或 context.xml |
应用的 web.xml 或注解 |
调用时机 | 在容器处理的早期阶段(路由前) | 在 Servlet 调用前(路由后) |
依赖环境 | 依赖 Tomcat API(Request 等) |
依赖 Servlet 规范 API |
扩展性 | 可影响所有应用(如 Engine 级别) | 仅影响当前应用 |
v1.3.10