责任链模式完全指南:从 if/else 地狱到中间件管道的工程演化

责任链模式可能是行为型模式里"应用密度最高"的一个 —— Express / Koa / Spring Security / Servlet Filter / Netty Pipeline / Redux 中间件,几乎所有需要"一串处理器依次处理同一个请求"的地方,你都能找到它。这篇文章把责任链的本质讲透,从最朴素的 if/else 重构开始,走到 HTTP 中间件、审批流、异常处理链,讲清楚它和管道、过滤器、装饰器的边界。

问题:if/else 长得像意大利面

看一段处理用户请求的代码,常见而典型:

public Response handle(Request req) {
    // 1. 校验登录
    if (req.getToken() == null) return Response.unauthorized();
    User user = validateToken(req.getToken());

    // 2. 校验权限
    if (!user.hasRole("admin")) return Response.forbidden();

    // 3. 限流
    if (rateLimiter.isExceeded(user.getId())) return Response.tooManyRequests();

    // 4. 参数校验
    if (req.getBody() == null || !isValid(req.getBody())) return Response.badRequest();

    // 5. 缓存查询
    Response cached = cache.get(req.cacheKey());
    if (cached != null) return cached;

    // 6. 真正的业务逻辑
    Response resp = businessLogic(req);

    // 7. 写日志
    auditLog.write(req, resp);
    return resp;
}

所有横切关注点都堆在一个方法里。问题:

  • 这个方法做太多事,违反单一职责。
  • 每个 if 之间互相纠缠 —— 想换顺序、想加一步、想跳过某步都得动这个大方法。
  • 很难复用 —— 别的 endpoint 也要登录 + 权限,但流程里某些步骤不一样。
  • 测试要构造一堆假数据走完整链路。

责任链模式说:把每个步骤拆成一个独立的"处理器",串成一条链。请求依次经过每个处理器,任一处理器可以处理它或传给下一个

责任链的标准结构

// 1. 抽象处理器
abstract class Handler {
    protected Handler next;

    public Handler setNext(Handler next) {
        this.next = next;
        return next;     // 返回 next 是为了链式 setNext
    }

    public Response handle(Request req) {
        if (next != null) return next.handle(req);
        return null;     // 链尾
    }
}

// 2. 具体处理器:每个只关心一件事
class AuthHandler extends Handler {
    public Response handle(Request req) {
        if (req.getToken() == null) return Response.unauthorized();
        req.setUser(validateToken(req.getToken()));
        return super.handle(req);    // 传给下一个
    }
}

class RoleHandler extends Handler {
    public Response handle(Request req) {
        if (!req.getUser().hasRole("admin")) return Response.forbidden();
        return super.handle(req);
    }
}

class RateLimitHandler extends Handler {
    public Response handle(Request req) {
        if (rateLimiter.isExceeded(req.getUser().getId())) return Response.tooManyRequests();
        return super.handle(req);
    }
}

class CacheHandler extends Handler {
    public Response handle(Request req) {
        Response cached = cache.get(req.cacheKey());
        if (cached != null) return cached;                   // 短路返回
        Response resp = super.handle(req);
        if (resp.isCacheable()) cache.put(req.cacheKey(), resp);
        return resp;
    }
}

class BusinessHandler extends Handler {
    public Response handle(Request req) {
        return businessLogic(req);                            // 链尾,真正干活
    }
}

// 3. 组装链
Handler chain = new AuthHandler();
chain.setNext(new RoleHandler())
     .setNext(new RateLimitHandler())
     .setNext(new CacheHandler())
     .setNext(new BusinessHandler());

// 4. 触发
Response resp = chain.handle(req);

对比改造前后:每个 Handler 短而聚焦,顺序由组装决定;新加一步 = 新加一个 Handler;某 endpoint 不需要权限校验,组装时跳过 RoleHandler 即可。

责任链的两种语义

不同实现下,责任链表现出两种不同的语义,要分清:

纯责任链:某一个处理者负责到底

请求沿链流动,直到某个处理者认为"我能处理"就吃掉它,后续不再经过。例:异常处理(每层 catch 不到就向上传),日志级别过滤(找到第一个能处理这个 level 的 logger)。

// Logger 责任链
abstract class Logger {
    int level;
    Logger next;
    public Logger setNext(Logger n) { next = n; return n; }
    public void log(int level, String msg) {
        if (this.level <= level) write(msg);
        if (next != null) next.log(level, msg);          // 全链流动
    }
    abstract void write(String msg);
}

过滤链 / 管道:所有处理者都参与

请求依次经过每个处理者,每个都可以读写、装饰、短路。Servlet Filter、Express 中间件就是这种。上面 AuthHandler / CacheHandler 的例子也是。

区别在于:纯责任链强调"找到第一个能处理的",过滤链强调"按顺序全部经过"。前者是"或",后者是"与"。工程里后者更常见,因为它对横切关注点的支持更天然。

实战 1:Express / Koa 中间件

Node.js 后端开发的"日常责任链":

// Express
app.use(express.json());                           // 解析 JSON body
app.use(cors());                                    // CORS
app.use(morgan('combined'));                       // 日志
app.use(authMiddleware);                           // 鉴权
app.use('/api', rateLimitMiddleware);              // 限流(部分路径)
app.use(errorHandler);                              // 错误处理(末端)

app.get('/api/users', (req, res) => { ... });      // 业务

// 自定义中间件
function authMiddleware(req, res, next) {
    if (!req.headers.authorization) return res.status(401).send();
    req.user = decode(req.headers.authorization);
    next();                                          // 传递给下一个
}

// Koa 用 async/await,语义更清晰
app.use(async (ctx, next) => {
    const start = Date.now();
    await next();                                    // 等下游处理完
    console.log(`${ctx.method} ${ctx.url} ${Date.now() - start}ms`);
});

Koa 的"洋葱模型"特别值得记:next() 是异步等待,所以可以在调用前后都执行代码 —— 一个中间件像"洋葱皮"包裹着下游,请求一层层进、响应一层层出。这种结构让"前置 + 后置"逻辑非常自然。

实战 2:Spring Security 过滤链

Spring Security 是一条由 20+ 个 Filter 组成的责任链:

// 部分核心 Filter 顺序(简化)
SecurityContextPersistenceFilter   // 从 session 加载 SecurityContext
HeaderWriterFilter                  // 设置 X-Frame-Options 等头
CsrfFilter                          // CSRF 校验
LogoutFilter                        // /logout 端点处理
UsernamePasswordAuthenticationFilter   // 表单登录处理
BasicAuthenticationFilter           // HTTP Basic
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
RememberMeAuthenticationFilter      // RememberMe cookie
AnonymousAuthenticationFilter       // 匿名用户处理
SessionManagementFilter             // 会话管理
ExceptionTranslationFilter          // 安全异常 -> HTTP 响应
FilterSecurityInterceptor           // 最终的访问控制决策

每个 Filter 只做一件事。你想加自定义 Token 校验?写一个 Filter 插进合适位置就行,不改其他 Filter。这种"高度可插拔"就是责任链的核心价值。

实战 3:审批流

请假审批的经典需求:小额组长批,中额部门经理批,大额 HR + CEO 批。

abstract class Approver {
    protected Approver next;
    public Approver setNext(Approver n) { this.next = n; return n; }

    public final void process(LeaveRequest req) {
        if (canApprove(req)) approve(req);
        else if (next != null) next.process(req);
        else reject(req);
    }
    abstract boolean canApprove(LeaveRequest req);
    abstract void approve(LeaveRequest req);
    void reject(LeaveRequest req) { System.out.println("无人可批"); }
}

class TeamLead extends Approver {
    boolean canApprove(LeaveRequest r) { return r.getDays() <= 3; }
    void approve(LeaveRequest r) { System.out.println("组长批准"); }
}

class Manager extends Approver {
    boolean canApprove(LeaveRequest r) { return r.getDays() <= 7; }
    void approve(LeaveRequest r) { System.out.println("部门经理批准"); }
}

class HRDirector extends Approver {
    boolean canApprove(LeaveRequest r) { return r.getDays() <= 30; }
    void approve(LeaveRequest r) { System.out.println("HR 总监批准"); }
}

Approver chain = new TeamLead();
chain.setNext(new Manager()).setNext(new HRDirector());

chain.process(new LeaveRequest(2));    // 组长批准
chain.process(new LeaveRequest(5));    // 部门经理批准
chain.process(new LeaveRequest(15));   // HR 总监批准
chain.process(new LeaveRequest(60));   // 无人可批

这是"纯责任链"的典型 —— 每个 Approver 决定要不要处理,处理了就不再往下。

实战 4:异常处理 / 全局错误兜底

app.use((err, req, res, next) => {
    if (err instanceof ValidationError) {
        return res.status(400).json({ code: 'VALIDATION', message: err.message });
    }
    next(err);    // 不处理就传下去
});

app.use((err, req, res, next) => {
    if (err instanceof AuthError) {
        return res.status(401).json({ code: 'AUTH', message: err.message });
    }
    next(err);
});

app.use((err, req, res, next) => {
    console.error('未捕获错误', err);
    res.status(500).json({ code: 'INTERNAL', message: 'something went wrong' });
});

错误处理链让你能按异常类型分别处理,且新加异常类型时不影响其他处理器。这是责任链在 Web 框架里最常见的应用之一。

实战 5:Netty Pipeline

Netty 的 ChannelPipeline 是责任链的另一种工业级实现 —— 每个 ChannelHandler 处理 inbound 或 outbound 事件,可以解码、加密、路由、限流等:

ServerBootstrap b = new ServerBootstrap();
b.childHandler(new ChannelInitializer<SocketChannel>() {
    protected void initChannel(SocketChannel ch) {
        ch.pipeline()
            .addLast(new LineBasedFrameDecoder(1024))    // 拆帧
            .addLast(new StringDecoder())                 // 字节 -> 字符串
            .addLast(new StringEncoder())                 // 字符串 -> 字节
            .addLast(new BusinessHandler());              // 业务
    }
});

同一条连接的所有读写都顺着 pipeline 走,inbound 从前往后,outbound 从后往前。这种"双向责任链"是高性能网络编程的标配设计。

责任链 vs 装饰器 vs 命令

这三个容易混:

  • 责任链:多个处理器串联,每个决定要不要处理 / 短路。重点是"谁来处理"。
  • 装饰器:多层包装同一个对象,每层加点行为。重点是"增强了什么"。
  • 命令:把"操作"封装成对象,可以排队、撤销、记录。重点是"把操作做成数据"。

装饰器和责任链在结构上有很大相似性(都是嵌套包装),但意图不同 —— 装饰器一定会调内层,责任链可能短路。

动态构建责任链

实际项目里链常常需要按配置或权限动态构建:

List<Handler> chain = new ArrayList<>();
chain.add(new AuthHandler());
if (config.rateLimitEnabled()) chain.add(new RateLimitHandler());
if (user.isVip()) chain.add(new VipPreprocessHandler());
chain.add(new BusinessHandler());

// 用 List 风格执行,而不是链表
public Response execute(List<Handler> chain, Request req, int idx) {
    if (idx >= chain.size()) return null;
    return chain.get(idx).handle(req, () -> execute(chain, req, idx + 1));
}

这种"List + 索引"实现比"链表 + next 指针"更灵活 —— 可以反向迭代、可以跳到任意位置、可以动态插拔。Express、Koa 的中间件就是 List 实现,不是链表。

函数式责任链:Lambda 即处理器

// JS 风格
type Handler = (req: Request, next: () => Response) => Response;

function compose(handlers: Handler[]): Handler {
    return (req, terminal) => {
        function next(i: number): Response {
            if (i >= handlers.length) return terminal();
            return handlers[i](req, () => next(i + 1));
        }
        return next(0);
    };
}

const middleware = compose([
    (req, next) => { if (!req.token) return unauthorized(); return next(); },
    (req, next) => { console.log('handling'); return next(); },
    (req, next) => processBusinessLogic(req),
]);

middleware(request, () => defaultResponse());

这种实现没有类、没有继承,纯靠高阶函数。Express / Koa / Redux 中间件本质就是这种结构。

常见坑

坑 1:忘了 next。 Express 中间件最常见的 bug:req.user = ...; 但没调 next(),请求挂在那里直到超时。每个中间件要么调 next,要么显式响应(不调 next),不能两个都不做。

坑 2:next 调多次。 同样的中间件如果不小心调了两次 next,可能导致下游异步处理重复运行。规范:next 调一次就立刻 return。

坑 3:错误在异步链路里被吞。 中间件里 throw 同步异常框架能 catch,但 await 出来的异常如果没 try/catch,默默丢失。Koa 用 async/await,可以让框架统一捕获;Express 要么显式 .catch(next),要么用 express-async-errors。

坑 4:链太长难调试。 一个请求穿过 20 个中间件,出错时栈跟踪深得没法看。建议加 request-id,每个中间件入口打印 "[reqId] handler-name",出错能立刻定位是哪一步。

坑 5:中间件之间隐式依赖。 "RateLimitHandler 必须在 AuthHandler 之后"是个约定,但代码里看不出来。如果有人换了顺序,bug 就出现了。可以让 Handler 声明 requires() 让框架检查依赖,或者写文档明示。

测试责任链

// 测试一个中间件
@Test
public void authMiddleware_rejects_unauthorized() {
    AuthHandler h = new AuthHandler();
    Handler next = mock(Handler.class);
    h.setNext(next);

    Request req = new Request();
    req.setToken(null);

    Response resp = h.handle(req);

    assertEquals(401, resp.getStatus());
    verify(next, never()).handle(any());     // 不应调用下游
}

每个 Handler 独立测试,verify 它"调或不调"下游 —— 这是责任链可测性的标志。

写在最后

责任链模式不只是设计模式,它是"一切横切关注点的标准答案"。鉴权、限流、缓存、日志、监控、错误处理、Tracing —— 这些通用能力如果都写在业务方法里,业务代码就被淹没;放进责任链 / 中间件,业务方法瞬间变干净。

给一个工程信号:当一段代码在多个 endpoint / Service / 方法里反复出现"先做 X 检查再做实际工作",而 X 又是和业务无关的横切逻辑 —— 就是责任链在喊你。把这些 X 抽到中间件 / Handler 链里,业务代码就只剩业务逻辑。这种"关注点分离"是现代后端框架最重要的设计哲学,而它的具体实现,几乎全靠责任链模式。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

代理模式完全指南:从静态代理到 Spring AOP 的工程化巅峰

2026-5-15 11:57:56

技术教程

命令模式完全指南:从 Undo/Redo 到 Event Sourcing 与 CQRS

2026-5-15 15:29:20

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索