当前位置: 首页 > news >正文

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 个设计模式用漫画 + 答题的方式讲,感兴趣可以搜一下看看。

http://www.rkmt.cn/news/1518452.html

相关文章:

  • 承德隆化重卡维修标杆|解放重汽陕汽维修 承围线交叉口门店 24小时全天候货车救援维修服务 电话15831485236 - 速递信息
  • 【洛谷 P2249】查找(深基 13. 例 1)+ 详细分析
  • zxing-cpp跨平台实战:C++20赋能的多端条码处理库深度解析
  • VS Code语法检查进阶指南:Grammarly插件深度解析与实战应用
  • 四川成都市十大单招培训学校排名TOP10 - 四川单招培训
  • MC68377 QADC64模块时钟与中断机制深度解析与实战配置
  • 2026西安本地宝藏回收店,闲置奢品变现不用愁 - 讯息早知道
  • I2C中断机制深度解析:从轮询到事件驱动的效率跃迁
  • 构建高可扩展的插件化图片编辑系统:基于fabric.js和Vue的解耦架构实践
  • 靠谱的永康纯钛保鲜盒实力公司 - 速递信息
  • 2026年设备 + 施工一站式,通风排风定制服务推荐 - 速递信息
  • 如何通过自动化脚本高效获取Oracle Cloud免费ARM服务器
  • USB-Disk-Ejector:终极Windows USB设备安全弹出解决方案
  • Unity卡牌游戏UI开发:从技术痛点到优雅解决方案
  • 舟山汽车内饰翻新|广粤汽车真皮内饰改装本地改装实测 - 百航
  • 技术解密:Cursor AI编程助手机器码刷新机制与实战突破
  • 终极指南:15分钟快速上手 wangEditor v5 富文本编辑器完整教程
  • 扒透Claude‑Code底层原理,读懂Agent的消息运行机制
  • MC68341定时器模块:可变宽度单脉冲生成与脉冲宽度测量实战解析
  • 革命性开源5G测试平台:UERANSIM如何让5G研发变得简单高效
  • 深入解析MC9328MX1 UART驱动:从寄存器配置到中断处理的嵌入式实战
  • Linux jbd2_journal_recover日志恢复与superblock标记
  • UUV Simulator终极指南:快速构建高保真水下机器人仿真系统
  • 2026年6月南通劳保手套工厂排行:服务与品质双维度深度盘点 - 奔跑123
  • Python 高手编程系列三千三百七十八:构建自己的文档集
  • 2026年6月国内松木镜框油画布框套装定制服务商排行top5,资质与专业评测推荐 - 奔跑123
  • 2026 青岛汽车音响改装靠谱度榜首:鼎峰汇汽车音响,被低估的技术标杆 - 汽车音响改装
  • 如何快速部署AI模型到嵌入式设备:5大实用技巧与RKNN-Toolkit2终极指南
  • 2026石家庄翡翠回收深度实测:七家机构种水色工专项横评 - 薛定谔的梨花猫
  • DLSS Swapper终极指南:智能游戏性能优化方案