设计模式与Tomcat:Pipeline中的责任链、Lifecycle中的观察者、Connector中的适配器
说实话,Tomcat 是我早期啃源码时最头疼的东西之一。那时候我刚从学校出来,觉得能写个 Servlet 就挺牛了。直到有一天线上 Tomcat 莫名其妙挂掉,我被迫去翻它的启动流程……嗯,那才是我真正理解设计模式的开端。
今天咱们就聊聊 Tomcat 里三个最经典的模式应用:责任链、观察者、适配器。这三个模式在 Tomcat 里不是生搬硬套的,而是为了解决真实问题长出来的。
Pipeline 与责任链模式
先说说 Pipeline。Tomcat 处理一个请求,不是一步到位的。它要经过一堆阀门(Valve),比如日志、认证、权限、解析……每个阀门干完自己的活,再传给下一个。
这不就是责任链模式吗?
我当年第一次看 Pipeline 源码时,心里想的是:这不就是个链表吗?每个 Valve 持有下一个 Valve 的引用,调用 invoke() 时传下去。但仔细看才发现,Tomcat 的设计更讲究——它把 Pipeline 和 Valve 分开了。
核心设计:Pipeline 是链条的容器,Valve 是链条上的节点。Pipeline 负责管理 Valve 的增删,Valve 只关心自己的业务逻辑。
来看一段简化后的代码,你就明白了:
// 阀门接口
public interface Valve {
void invoke(Request request, Response response, ValveChain chain);
}
// 管道接口
public interface Pipeline {
void addValve(Valve valve);
Valve getFirst();
}
// 基础阀门实现
public class AccessLogValve implements Valve {
@Override
public void invoke(Request req, Response res, ValveChain chain) {
// 记录访问日志
System.out.println("Access log: " + req.getUri());
// 传给下一个阀门
chain.invokeNext(req, res);
}
}
这里有个细节我特别想提:ValveChain 这个对象。它其实是个迭代器,内部维护了当前执行到的位置。每个 Valve 调用 chain.invokeNext() 时,链条就往前走一步。
我在项目中遇到过类似场景——一个支付网关,需要经过风控、费率计算、渠道分发等多个步骤。当时我直接抄了 Tomcat 的 Pipeline 设计,效果非常好。每个步骤独立测试,新增一个阀门只需要加一行配置。
避坑指南:我曾经在责任链里搞过「全局异常捕获」,结果某个阀门抛异常后,整个链条断了,后面的阀门全没执行。后来我改成每个阀门自己 try-catch,异常信息包装后继续往下传。记住:责任链的每个节点都应该独立处理异常,不要指望上游帮你兜底。
Lifecycle 与观察者模式
Tomcat 的组件是有生命周期的。启动、初始化、开始、停止、销毁……每个阶段都要通知一堆监听器。比如你启动 Tomcat 时,它要通知 JMX 注册 MBean,通知日志系统初始化,通知连接器打开端口。
这就是观察者模式。
Tomcat 定义了一个 Lifecycle 接口,里面有几个核心方法:init()、start()、stop()、destroy()。每个组件实现这个接口,然后注册一堆 LifecycleListener。
我画了一张图,帮你理清这个关系:
你看,LifecycleBase 是抽象基类,它维护了一个监听器列表。当 start() 被调用时,它会遍历所有监听器,挨个调用 lifecycleEvent() 方法。
代码大概长这样:
public abstract class LifecycleBase implements Lifecycle {
private List<LifecycleListener> listeners = new ArrayList<>();
public void addLifecycleListener(LifecycleListener listener) {
listeners.add(listener);
}
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : listeners) {
listener.lifecycleEvent(event);
}
}
public final synchronized void start() {
// 状态检查...
fireLifecycleEvent(BEFORE_START_EVENT, null);
startInternal(); // 子类实现具体逻辑
fireLifecycleEvent(AFTER_START_EVENT, null);
setState(LifecycleState.STARTED);
}
}
我个人习惯把这种设计叫做「事件驱动的初始化」。你想想看,如果没有观察者模式,每个组件启动时都要硬编码调用一堆初始化逻辑,耦合度会高得吓人。
注意:Tomcat 的 Lifecycle 事件是同步的。也就是说,监听器里如果抛异常,整个启动流程会中断。我曾经踩过这个坑——一个自定义监听器里做了网络请求,超时了,结果 Tomcat 启动到一半卡死了。后来我改成异步处理,或者至少加个超时机制。
Connector 与适配器模式
最后聊聊 Connector。Tomcat 要支持多种协议:HTTP、AJP、HTTPS……每种协议的请求格式都不一样。但到了 Servlet 容器内部,它只认 ServletRequest 和 ServletResponse。
怎么把不同协议的请求转成统一的接口?
适配器模式登场。
Tomcat 的 CoyoteAdapter 就是干这个的。它把底层 Socket 来的字节流,解析成 org.apache.catalina.connector.Request 对象,再传给 Pipeline。
我举个例子,HTTP 请求长这样:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
AJP 协议是二进制的,格式完全不同。但经过适配器之后,到了 Servlet 手里,都是 request.getMethod()、request.getRequestURI()。
适配器的核心代码其实很简单:
public class CoyoteAdapter implements Adapter {
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) {
// 1. 将 coyote 的 Request 转换为 catalina 的 Request
Connector connector = getConnector();
Request catalinaRequest = connector.createRequest();
catalinaRequest.setCoyoteRequest(req);
// 2. 设置上下文路径、URI 等
catalinaRequest.getMappingData().setContextPath(...);
// 3. 交给 Pipeline 处理
connector.getService().getContainer().getPipeline()
.getFirst().invoke(catalinaRequest, catalinaResponse);
}
}
说白了,适配器就是一层「翻译」。它不改变底层协议,只是让上层接口统一。我在做微服务网关时也用过类似思路——把 HTTP、gRPC、Dubbo 的请求统一转成内部 RPC 调用,网关核心逻辑完全不用关心底层协议。
我的经验:适配器模式最容易犯的错误是「过度适配」。我曾经见过一个项目,为了统一接口,把完全不同的业务逻辑也塞进适配器里,结果适配器变得又臭又长。记住:适配器只做格式转换,不做业务处理。业务逻辑应该交给后面的 Pipeline 或 Service 层。
三个模式如何协同
你可能会问:这三个模式在 Tomcat 里是各自为战,还是互相配合?
答案是:它们串成了一条完整的请求处理链路。
我画个流程图你就明白了:
你看,整个流程非常清晰:
- Connector 接收网络请求,拿到原始字节流
- CoyoteAdapter 把字节流解析成统一的 Request/Response 对象
- Pipeline 带着 Request 穿过一个个 Valve,做各种前置处理
- 最终到达 Servlet,执行你的业务代码
而 Lifecycle 观察者模式,则负责管理这些组件从生到死的整个过程。启动时按顺序初始化,关闭时按顺序清理。
说实话,Tomcat 的架构设计是我见过最优雅的之一。它没有过度设计,每个模式都用得恰到好处。你想想看,如果让你从头写一个 Web 服务器,你能把这三个模式用得这么自然吗?
总结一下:
- Pipeline + Valve = 责任链模式,解决请求处理流程的灵活编排
- Lifecycle + Listener = 观察者模式,解决组件生命周期的事件通知
- Connector + Adapter = 适配器模式,解决多协议统一接入
这三个模式在 Tomcat 里不是孤立的,它们共同构建了一个高内聚、低耦合的架构。下次你写中间件或者框架时,不妨想想 Tomcat 是怎么做的。
公众号:蓝海资料掘金营,微信deep3321