0%

Pipeline和Valve

Tomcat 中的 Pipeline 与 Valve:请求处理的责任链模式

Tomcat 的 Pipeline(管道)和 Valve(阀门)是其核心组件,用于处理请求和响应的流程控制。它们基于责任链模式设计,将请求处理的不同任务拆解为独立的阀门,通过管道串联执行,实现了请求处理逻辑的解耦与灵活扩展。本文将深入解析 Pipeline 与 Valve 的工作原理、结构及配置方式。

Pipeline 与 Valve 的核心概念

基本定义

  • Pipeline(管道)
    每个 Tomcat 容器(EngineHostContextWrapper)都包含一个 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
2
3
4
5
6
7
8
9
public class StandardEngine extends ContainerBase implements Engine {
// 每个容器初始化时创建对应的 Pipeline
protected final Pipeline pipeline = new StandardPipeline(this);

// 设置 Engine 的 Basic Valve
public StandardEngine() {
pipeline.setBasic(new StandardEngineValve());
}
}

Valve 的链式存储与调用

Valve 在 Pipeline 中以单向链表的形式存储,通过 getNext()setNext() 维护顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StandardPipeline implements Pipeline {
private Valve first = null; // 第一个 Valve
private Valve basic = null; // Basic Valve(最后一个)

// 添加 Valve 到管道(插入到 first 与 basic 之间)
public void addValve(Valve valve) {
if (first == null) {
first = valve;
valve.setNext(basic); // 新 Valve 的下一个是 basic
} else {
Valve current = first;
// 遍历到最后一个非 basic 的 Valve
while (current.getNext() != basic) {
current = current.getNext();
}
current.setNext(valve); // 当前 Valve 的下一个指向新 Valve
valve.setNext(basic); // 新 Valve 的下一个指向 basic
}
}
}

调用流程
当请求进入容器时,Pipeline 从第一个 Valve 开始调用,依次执行每个 Valve 的 invoke() 方法,最终到达 Basic Valve:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Valve 接口的核心方法
public interface Valve {
void invoke(Request request, Response response) throws IOException, ServletException;
Valve getNext();
void setNext(Valve valve);
}

// 示例 Valve 的实现(简化版)
public class MyValve implements Valve {
private Valve next;

@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
// 1. 执行当前 Valve 的任务(如日志记录)
System.out.println("处理请求:" + request.getRequestURI());

// 2. 传递给下一个 Valve
if (next != null) {
next.invoke(request, response);
}
}

// getNext() 和 setNext() 实现略
}

请求处理的完整流程(多层 Pipeline 协作)

一个 HTTP 请求在 Tomcat 中的处理需要经过多层容器的 Pipeline,流程如下:

  1. 请求进入 Engine 容器
    • Engine 的 Pipeline 执行一系列 Valve(如 AccessLogValve 记录访问日志)。
    • 最终由 StandardEngineValve(Basic Valve)根据域名将请求路由到对应的 Host
  2. 请求进入 Host 容器
    • Host 的 Pipeline 执行 Valve(如 SingleSignOn 处理单点登录)。
    • StandardHostValve(Basic Valve)根据请求路径路由到对应的 Context
  3. 请求进入 Context 容器
    • Context 的 Pipeline 执行 Valve(如 RemoteAddrValve 限制 IP 访问)。
    • StandardContextValve(Basic Valve)根据 Servlet 映射路由到对应的 Wrapper
  4. 请求进入 Wrapper 容器
    • Wrapper 的 Pipeline 执行 Valve(如 RequestDumperValve 打印请求详情)。
    • StandardWrapperValve(Basic Valve)加载并调用目标 Servlet 的 service() 方法。

示意图

1
2
3
4
5
请求 → Engine Pipeline [Valve1 → Valve2 → ... → StandardEngineValve] 
→ Host Pipeline [ValveA → ValveB → ... → StandardHostValve]
→ Context Pipeline [ValveX → ValveY → ... → StandardContextValve]
→ Wrapper Pipeline [ValveM → ValveN → ... → StandardWrapperValve]
→ 调用 Servlet

Valve 的配置与常用实现

配置 Valve(以 Host 为例)

Valve 可在 server.xml 中对应容器下配置,例如为 Host 添加访问日志 Valve:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!-- 访问日志 Valve:记录请求信息到日志文件 -->
<Valve
className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="localhost_access_log"
suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b"
/>

<!-- 远程地址限制 Valve:仅允许 192.168.x.x 网段访问 -->
<Valve
className="org.apache.catalina.valves.RemoteAddrValve"
allow="192\.168\.\d+\.\d+"
/>
</Host>
  • className:Valve 的实现类全路径。
  • 其他属性:Valve 特有的配置(如日志路径、允许的 IP 规则)。

常用 Valve 实现

Tomcat 内置了多种实用 Valve,覆盖日志、安全、性能等场景:

Valve 类名 作用说明
AccessLogValve 记录访问日志(IP、请求路径、响应码等),支持自定义格式。
RemoteAddrValve 根据客户端 IP 地址允许 / 拒绝访问(如限制内网访问)。
RemoteHostValve 根据客户端主机名允许 / 拒绝访问。
SingleSignOn 实现多 Web 应用间的单点登录(共享认证信息)。
RequestDumperValve 打印请求 / 响应的详细信息(用于调试)。
ErrorReportValve 生成友好的错误页面(如 404、500 错误)。
SlowAccessValve 记录慢请求(超过指定时间的请求),用于性能排查。

自定义 Valve 开发

若内置 Valve 无法满足需求,可自定义 Valve 扩展功能,步骤如下:

  1. 实现 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
    24
    import 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 {
    @Override
    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");
    }
    }
  2. 编译与部署
    将类打包为 JAR,放入 $CATALINA_BASE/lib 目录。

  3. 配置使用
    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.xmlcontext.xml 应用的 web.xml 或注解
调用时机 在容器处理的早期阶段(路由前) 在 Servlet 调用前(路由后)
依赖环境 依赖 Tomcat API(Request 等) 依赖 Servlet 规范 API
扩展性 可影响所有应用(如 Engine 级别) 仅影响当前应用

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10