news 2026/6/13 17:39:53

Tomcat 的 Pipeline 比你写的责任链复杂10倍

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Tomcat 的 Pipeline 比你写的责任链复杂10倍

很多人学责任链模式,写出来的就是 Handler1 → Handler2 → Handler3,一路调下去。这不叫责任链,这叫 for 循环套了个壳。

Tomcat 的 Pipeline-Valve 机制才是责任链模式的真正形态。它跟你写的 Handler 链至少有三个本质区别:可中断、可分支、有容器层级。

Pipeline-Valve:不是 Handler 链,是阀门链

Tomcat 的请求处理流程是这样的:

Connector → Engine Pipeline → Host Pipeline → Context Pipeline → Wrapper Pipeline → Servlet

每个容器(Engine/Host/Context/Wrapper)都有一个 Pipeline,Pipeline 里有多个 Valve。请求从第一个 Valve 进入,每个 Valve 可以选择:

  1. 继续传给下一个 Valve
  2. 直接返回(中断链)
  3. 在调用下一个 Valve 前后加自己的逻辑

```java // Valve 接口——比 Handler 复杂在哪? public interface Valve { Valve getNext(); // 获取下一个 Valve void setNext(Valve valve); // 设置下一个 Valve void invoke(Request request, Response response) // 处理请求 throws IOException, ServletException; }

// Pipeline 接口 public interface Pipeline { Valve getFirst(); // 获取第一个 Valve void addValve(Valve valve); // 动态添加 Valve void removeValve(Valve valve); // 动态移除 Valve } ```

跟你写的 Handler 链对比一下:

| 特性 | Handler 链 | Pipeline-Valve | |------|-----------|----------------| | 链路构建 | 构造时固定 | 运行时动态增删 | | 中断能力 | 需要约定返回值 | 天然支持(不调 getNext) | | 容器层级 | 扁平 | 嵌套(Engine→Host→Context→Wrapper) | | 分支能力 | 无 | Valve 可以改变请求流向 |

关键差异:Valve 是动态增删的。你可以在运行时通过addValve()给某个容器加一个阀门,不需要重启。这个能力在运维场景下非常有用——比如临时加一个限流 Valve,流量降下来后再移除。

一个真实的 Valve:AccessLogValve

Tomcat 自带的 AccessLogValve 是一个很好的学习案例:

```java public class AccessLogValve extends ValveBase { @Override public void invoke(Request request, Response response) throws IOException, ServletException { // 1. 先调下一个 Valve(让请求继续处理) getNext().invoke(request, response);

// 2. 请求处理完后,记录访问日志 long duration = System.currentTimeMillis() - request.getCoyoteRequest().getStartTime(); log(request, response, duration); }

} ```

注意这里的执行顺序:先调用下一个 Valve,等它处理完了再记录日志。这就是 Valve 比 Filter 灵活的地方——你可以在后置处理里拿到 response 的状态码和耗时,Filter 要做到这一点需要包装 Response 对象。

我在一个项目里仿照这个模式写了一个 MetricsValve,统计每个请求的 QPS、延迟、错误率。因为是 Valve 而不是 Filter,所以可以拿到 Tomcat 内部的请求信息(比如哪个 Host/Context 处理的),这是 Filter 层面拿不到的。

模板方法模式:LifecycleBase

Tomcat 的组件生命周期管理是模板方法模式的经典实现:

```java public abstract class LifecycleBase implements Lifecycle { @Override public final void init() throws LifecycleException { if (!state.equals(LifecycleState.NEW)) { invalidTransition(LifecycleState.BEFORE_INIT_EVENT); } setStateInternal(LifecycleState.INITIALIZING, null, false); try { initInternal(); // 模板方法,子类实现 } catch (Throwable t) { setStateInternal(LifecycleState.FAILED, null, false); throw t; } setStateInternal(LifecycleState.INITIALIZED, null, false); }

protected abstract void initInternal() throws LifecycleException;

} ```

这里有一个设计细节值得学习:状态转换和异常处理在模板方法里统一管理。子类的initInternal()只需要关注自己的初始化逻辑,不用担心状态是否正确、异常时状态怎么回退。

这比你写一个init()方法然后在里面 if-else 判断状态要安全得多——因为状态转换的代码只存在于LifecycleBase一处,不可能不一致。

组合模式:Container 的层级结构

Tomcat 的 Engine → Host → Context → Wrapper 是典型的组合模式:

java public interface Container extends Lifecycle { Container getParent(); void addChild(Container child); void removeChild(Container child); Container findChild(String name); Container[] findChildren(); }

但 Tomcat 的组合模式比教科书多了一个限制:子容器的类型是固定的。Engine 只能包含 Host,Host 只能包含 Context,Context 只能包含 Wrapper。这不是组合模式的标准做法(标准做法是所有节点都是 Component 类型),但这个限制是必要的——Tomcat 需要保证请求从外到内逐层传递,不能跳层。

这个设计决策说明一个问题:模式是工具,不是教条。当模式跟业务约束冲突时,优先满足业务约束。

观察者模式:Lifecycle 事件

Tomcat 的生命周期事件是观察者模式,但有个反直觉的地方:事件是同步分发的

java public abstract class LifecycleBase implements Lifecycle { protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); for (LifecycleListener listener : listeners) { listener.lifecycleEvent(event); // 同步调用 } } }

很多人一看到"事件"就想到异步,但 Tomcat 选择同步是有原因的:组件的生命周期状态转换必须是确定性的。如果异步分发事件,监听器可能在状态已经改变之后才收到通知,导致基于旧状态做决策。

这个取舍在业务代码里也经常遇到:你需要"事件通知"的解耦能力,但又不接受异步带来的时序不确定性。Tomcat 的方案是:同步分发事件,但在事件处理中避免重操作。监听器只做轻量级的状态同步,重操作放到独立的线程池。

真正的教训

Tomcat 的设计模式用得比大部分项目都复杂,但不是因为 Tomcat 的开发者喜欢炫技。原因是 HTTP 服务器的需求本身就复杂:动态增删组件、嵌套容器、状态一致性、请求的灵活拦截……每个需求都逼着你选一个比"简单版"更重的方案。

你写的责任链是 Handler1→Handler2→Handler3,Tomcat 的责任链是动态阀门+嵌套管道+状态管理。差距不在于你不会写,而在于你的问题域没复杂到需要这种程度。

但反过来,如果你的问题域确实需要这些能力,而你还在用 Handler 链,那就是欠债。


我在做一个用卡皮巴拉讲设计模式的微信小程序「爪爪代码冒险记」,23 个设计模式用漫画 + 答题的方式讲,感兴趣可以搜一下看看。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 17:34:02

StreamCap架构设计深度解析:基于FFmpeg的多平台直播流录制技术实现

StreamCap架构设计深度解析:基于FFmpeg的多平台直播流录制技术实现 【免费下载链接】StreamCap Multi-Platform Live Stream Automatic Recording Tool | 多平台直播流自动录制客户端 基于FFmpeg 支持监控/定时/转码 项目地址: https://gitcode.com/gh_mirrors/…

作者头像 李华
网站建设 2026/6/13 17:34:00

嵌入式中断控制器高级特性:软件IACK与动态优先级重映射实战

1. 项目概述:中断控制器在嵌入式系统中的核心地位 在嵌入式系统开发中,尤其是对实时性有苛刻要求的领域,比如电机控制、汽车电子或者通信基站,中断处理能力往往是决定系统性能上限的关键。想象一下,你正在编写一个电机…

作者头像 李华
网站建设 2026/6/13 17:31:59

告别重复片头:Jellyfin智能跳过插件的完整使用指南

告别重复片头:Jellyfin智能跳过插件的完整使用指南 【免费下载链接】intro-skipper Fingerprint audio to automatically detect and skip intro sequences in Jellyfin 项目地址: https://gitcode.com/gh_mirrors/in/intro-skipper 你是否厌倦了每次观看连续…

作者头像 李华