什么是责任链模式?应用场景有哪些?
结论先行:责任链(Chain of Responsibility)把请求交给一条处理器链,每个节点独立判断"我来处理 / 我放行 / 我中断",直到有人处理或链走完。它把"一大坨 if-else 的优先级判断"变成"一条可增删、可排序的处理管线"。
1. 结构:请求沿链流动
Request ──►┌────────┐ next() ┌────────┐ next() ┌────────┐
│ 限流 │─────────►│ 鉴权 │─────────►│ 风控 │──► 业务
└───┬────┘ └───┬────┘ └───┬────┘
└── 任一处拒绝 / 401 / 拦截 → 直接返回 ──┘
两种落地形态:
| 形态 | 节点怎么串 | 代表实现 |
|---|---|---|
| 指针式(链表) | 每个 handler 持有 next 引用,自己调 next.handle() | 手写审批流、Netty ChannelHandler |
| 数组式(游标) | 框架持有 List<Filter> + 当前下标,统一 doFilter 推进 | Servlet FilterChain、Spring HandlerExecutionChain |
指针式更灵活(节点可动态换 next),数组式更容易做"前后置环绕"(chain.doFilter() 前后都能写代码)。
2. 指针式实现
abstract class Handler {
protected Handler next;
public Handler setNext(Handler next) { // 返回 next 便于链式拼装
this.next = next;
return next;
}
public abstract void handle(Order order);
protected void next(Order order) { // 子类放行时调用
if (next != null) {
next.handle(order);
}
}
}
class StockHandler extends Handler {
@Override
public void handle(Order order) {
if (order.getStock() < order.getQty()) {
System.out.println("库存不足,链路终止");
return; // 不调 next() = 中断
}
next(order); // 放行
}
}
class PayHandler extends Handler {
@Override
public void handle(Order order) {
System.out.println("扣款成功:" + order.getId());
}
}
// 拼装:库存 → 支付(风控节点同理,只是多一个中断条件)
Handler chain = new StockHandler();
chain.setNext(new PayHandler());
chain.handle(order);
3. 数组式实现(贴近 Servlet Filter)
interface Filter {
void doFilter(Request req, Response resp, FilterChain chain);
}
class FilterChain {
private final List<Filter> filters;
private int index = 0;
FilterChain(List<Filter> filters) { this.filters = filters; }
void doFilter(Request req, Response resp) {
if (index < filters.size()) {
Filter f = filters.get(index++); // 游标前移,保证只走一遍
f.doFilter(req, resp, this);
}
}
}
// 环绕型过滤器:doFilter 前后都能插逻辑
class AuthFilter implements Filter {
public void doFilter(Request req, Response resp, FilterChain chain) {
if (req.getToken() == null) { resp.setStatus(401); return; } // 不调 chain = 短路
chain.doFilter(req, resp); // 放行,回来还能做事
}
}
游标式必须"先取下标再前移",否则递归回来会重复执行同一节点。这也是自己手写 Filter 时最常见的 bug。
4. 和相邻模式的边界
| 对比项 | 责任链 | 装饰器 | 观察者 |
|---|---|---|---|
| 目的 | 找到能处理的人 | 叠加功能 | 广播通知 |
| 是否一定走完 | 不一定,可中断 | 一定走完 | 每个订阅者都调 |
| 节点关系 | 平级、有顺序 | 层层包裹 | 一对多 |
| 典型例子 | Filter、审批流 | BufferedReader 套 FileReader | 事件监听 |
5. 应用场景
| 场景 | 链上节点 |
|---|---|
| Web 请求处理 | 编码、鉴权、限流、日志、跨域(Servlet Filter) |
| Spring MVC | HandlerInterceptor 的 pre/post/afterCompletion |
| 网关 | 路由、鉴权、灰度、限流(Spring Cloud Gateway 的 GlobalFilter) |
| 网络层 | Netty ChannelPipeline 的 inbound/outbound handler |
| 流量防护 | Sentinel 的 SlotChain:统计、限流、熔断、系统保护依次判定 |
| 业务规则 | 订单校验、风控规则、审批流、报销多级签批 |
6. 常见追问
Q:链太长会不会有性能问题? 节点本身很轻(判空、查缓存)时可以忽略;真正的问题是远程调用型节点(每个节点一次 RPC),这时要合并节点或改成异步管线。
Q:怎么防止链走不完或死循环?
数组式用游标天然防重入;指针式要保证 setNext 不成环,可在 add 时做一次环检测,或限制最大跳数。
Q:某个节点抛异常,后面的还执行吗?
默认不执行。要"出异常也继续"就把 try-catch 放进节点内部,或用环绕式写法在 finally 里继续 chain.doFilter。网关通常选择"异常即中断并返回错误码"。
Q:所有节点都没处理怎么办? 链尾必须有一个兜底节点(返回 404 或默认策略),否则请求会静默丢失——这是线上最容易漏的一环。
小结
- 责任链 = 处理器串成链,各自决定处理、放行或中断。
- 两种形态:指针式(
next引用,灵活)、数组式(游标推进,便于环绕)。 - 场景集中在"管线式处理":Filter、Interceptor、网关、Netty、Sentinel。
- 追问重点:链长性能、防环、异常中断、兜底节点。