什么是责任链模式?应用场景有哪些?

#中间件#面试八股

什么是责任链模式?应用场景有哪些?

结论先行:责任链(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 MVCHandlerInterceptor 的 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。
  • 追问重点:链长性能、防环、异常中断、兜底节点。
笔记加载中…