GoF设计模式——责任链模式

本文是【GoF设计模式】系列第21篇,更多内容欢迎关注公众号:咖啡八杯

前言

为什么需要责任链模式?

假设要实现一个请假审批功能:1 天以内组长批,3 天以内经理批,7 天以内总监批,7 天以上总经理批。最直接的写法是在审批方法里堆一串 if-else

java 复制代码
public void approve(LeaveRequest request) {
    if (request.getDays() <= 1) {
        teamLeader.approve(request);
    } else if (request.getDays() <= 3) {
        manager.approve(request);
    } else if (request.getDays() <= 7) {
        director.approve(request);
    } else {
        generalManager.approve(request);
    }
}

看起来能跑,但问题藏在后面。第一,审批逻辑和审批人死死绑在客户端--新增一个"副总经理"层级,这段 if-else 必须改;调整天数阈值,也得改这里。第二,客户端被迫知道整条审批链的结构和每个人的权限范围,本该只管"提交申请",却被迫操心"谁先批、谁后批、每个人能批多少"。

问题症结在于把"请求的处理者"和"请求的发起者"焊在了一起--发送者必须清楚链上有谁、按什么顺序、各自管什么。责任链模式解决的就是这个问题:把多个处理者串成一条链,请求沿链传递,发送者只管把请求丢给链首,至于最终由谁处理,链自己决定。

概念

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式 ,核心思想是将请求的发送者和接收者解耦,让多个对象都有机会处理请求,将这些对象连成一条链,沿着链传递请求,直到有一个对象处理它为止

可以把它想象成公司里的报销审批:拿着报销单先找组长签字,组长权限不够就转给经理,经理不够再转给总监。申请人只管提交单子,不需要知道最终由谁签批;每个审批人也只管两件事--"这笔我能不能批"和"批不了转给谁"。

责任链模式涉及三个角色:

  • Handler(抽象处理者) :定义处理请求的接口,持有下一个处理者的引用,提供设置后继者的方法
  • ConcreteHandler(具体处理者) :实现处理逻辑,判断自己能否处理,能则处理,不能则转发给后继者
  • Client(客户端) :创建并组装处理者链,将请求发送给链首
classDiagram direction BT class Handler { <<abstract>> #next: Handler +setNext(Handler) Handler +handleRequest(Request) } class ConcreteHandlerA { +handleRequest(Request) } class ConcreteHandlerB { +handleRequest(Request) } class Client { +main() } ConcreteHandlerA --|> Handler : 继承 ConcreteHandlerB --|> Handler : 继承 Handler o--> Handler : next 持有后继 Client --> Handler : 发送请求

图中各类之间的关系:Handler 是抽象处理者,用一个 next 字段持有后继者的引用,并通过 setNext 把处理者串起来。ConcreteHandlerAConcreteHandlerB 都继承 Handler,各自实现 handleRequest 的判断逻辑--能处理就处理,不能就调用 next.handleRequest 把请求往后传。Client 只认识 Handler,把请求发给链首,不关心链上有几个处理者、各自是谁。请求就这样沿 next 引用一路传递,直到被某个处理者接收或到达链尾。

实现

责任链模式的核心是"请求与处理者解耦 + 链式传递"--抽象处理者统一维护后继引用和转发逻辑,子类只负责"我能不能处理"。根据请求被处理后是否继续传递,分纯责任链和不纯责任链两种。

标准实现

用抽象类作为 Handler 基类,统一维护 next 引用和转发逻辑;子类实现 handleRequest,判断自己能否处理,能则处理,不能则转发给 next。请求被一个处理者接收后不再继续传递--这是"纯责任链"。

java 复制代码
// 请求类
class Request {
    private int level;
    private String data;
    public Request(int level, String data) {
        this.level = level;
        this.data = data;
    }
    public int getLevel() { return level; }
    public String getData() { return data; }
}

// 抽象处理者
abstract class Handler {
    protected Handler next;

    public Handler setNext(Handler next) {
        this.next = next;
        return next;                   // 返回 next 支持链式调用
    }

    public abstract void handleRequest(Request request);
}

// 具体处理者A:处理 level <= 1 的请求
class ConcreteHandlerA extends Handler {
    public void handleRequest(Request request) {
        if (request.getLevel() <= 1) {
            System.out.println("ConcreteHandlerA 处理: " + request.getData());
        } else if (next != null) {
            next.handleRequest(request);
        } else {
            System.out.println("请求未被处理: " + request.getData());
        }
    }
}

// 具体处理者B:处理 level <= 2 的请求
class ConcreteHandlerB extends Handler {
    public void handleRequest(Request request) {
        if (request.getLevel() <= 2) {
            System.out.println("ConcreteHandlerB 处理: " + request.getData());
        } else if (next != null) {
            next.handleRequest(request);
        } else {
            System.out.println("请求未被处理: " + request.getData());
        }
    }
}

// 客户端:构建链并发送请求
class Client {
    public static void main(String[] args) {
        Handler handlerA = new ConcreteHandlerA();
        Handler handlerB = new ConcreteHandlerB();
        handlerA.setNext(handlerB);                          // 构建链:A -> B
        handlerA.handleRequest(new Request(1, "低级别请求")); // A 处理
        handlerA.handleRequest(new Request(2, "高级别请求")); // B 处理
        handlerA.handleRequest(new Request(3, "超限请求"));    // 无人处理
    }
}

角色对照

  • Handler(抽象处理者)Handler,持有 next 并提供 setNext
  • ConcreteHandler(具体处理者)ConcreteHandlerAConcreteHandlerB,各自判断 level 范围
  • Client(客户端)Client,组装链并发送请求

关键点setNext 返回 next 本身,因此 handlerA.setNext(handlerB).setNext(handlerC) 能一路链式串下去。抽象类统一维护 next 引用和转发逻辑,子类只写"我处理什么 + 处理不了转给 next"两行判断,不重复转发代码。链尾的 else 分支兜底处理无人接收的情况,避免请求"悬空"--这是纯责任链必须处理的问题。

💡 链的构建方式 :上例是手动 setNext 逐个拼接,实际项目里链往往不是手拼的。常见三种方式:手动 setNext(简单直观,适合链短且固定)、配置驱动(如 Spring 的 @Order 注解给处理者标序,容器自动按序收集成链)、枚举定义(用一个枚举类集中声明链顺序,改顺序只动枚举)。链越动态越长,越该用配置或枚举集中管理,避免散落各处的 setNext 难维护。

引入一个具体场景:医院的分诊台。病人挂号后,分诊台按病情轻重分流--普通感冒分给全科,需要手术的分给外科,危重病人分给急诊。每个科室只判断"这病我能不能看",看不了就往下一个科室转。这正是纯责任链"沿链寻找唯一处理者"的思路。

java 复制代码
// 请求:患者
class Patient {
    private String name;
    private int severity;                  // 病情严重程度:1轻 2中 3重

    public Patient(String name, int severity) {
        this.name = name;
        this.severity = severity;
    }
    public String getName() { return name; }
    public int getSeverity() { return severity; }
}

// 抽象处理者:科室
abstract class Department {
    protected Department next;

    public Department setNext(Department next) {
        this.next = next;
        return next;
    }

    public abstract void treat(Patient patient);
}

// 全科:处理 severity == 1
class GeneralPractice extends Department {
    public void treat(Patient patient) {
        if (patient.getSeverity() == 1) {
            System.out.println("全科接诊: " + patient.getName());
        } else if (next != null) {
            next.treat(patient);
        } else {
            System.out.println("无人接诊: " + patient.getName());
        }
    }
}

// 外科:处理 severity == 2
class Surgery extends Department {
    public void treat(Patient patient) {
        if (patient.getSeverity() == 2) {
            System.out.println("外科接诊: " + patient.getName());
        } else if (next != null) {
            next.treat(patient);
        }
    }
}

// 急诊:处理 severity == 3
class Emergency extends Department {
    public void treat(Patient patient) {
        if (patient.getSeverity() == 3) {
            System.out.println("急诊接诊: " + patient.getName());
        } else if (next != null) {
            next.treat(patient);
        }
    }
}

// 客户端:分诊台
class TriageDesk {
    public static void main(String[] args) {
        Department gp = new GeneralPractice();
        Department surgery = new Surgery();
        Department emergency = new Emergency();
        gp.setNext(surgery).setNext(emergency);              // 全科 -> 外科 -> 急诊
        gp.treat(new Patient("张三", 1));                    // 全科接诊
        gp.treat(new Patient("李四", 2));                    // 外科接诊
        gp.treat(new Patient("王五", 3));                    // 急诊接诊
    }
}

角色对照

  • Handler(抽象处理者)Department,持有 next 并提供 setNext
  • ConcreteHandler(具体处理者)GeneralPracticeSurgeryEmergency,各自按 severity 接诊
  • Client(客户端)TriageDesk,组装链并发送患者

关键点gp.setNext(surgery).setNext(emergency) 一行就把整条分诊链串好,新增一个"儿科"只需再 setNext 一次,老科室代码一行不改--这就是开闭原则的体现。客户端只和链首 gp 打交道,完全不感知链的长度和顺序。

不纯责任链

GoF 标准实现是纯责任链--请求被一个处理者接收后不再传递。但实际项目里更常见的是"不纯责任链"--每个处理者都对请求做一部分处理,然后继续传递,如 Servlet Filter、Spring Interceptor。它的写法借用模板方法模式的思路:handleRequest 固定"先处理再传递"的流程,子类只实现 doHandle 负责自己的那部分逻辑。

java 复制代码
// 抽象处理者(不纯责任链)
abstract class Handler {
    protected Handler next;

    public Handler setNext(Handler next) {
        this.next = next;
        return next;
    }

    public void handleRequest(Request request) {
        doHandle(request);                 // 先处理自己的部分
        if (next != null) {
            next.handleRequest(request);   // 再继续传递
        }
    }

    protected abstract void doHandle(Request request);
}

// 具体处理者:记录日志
class LogHandler extends Handler {
    protected void doHandle(Request request) {
        System.out.println("记录日志: " + request.getData());
    }
}

// 具体处理者:权限校验
class AuthHandler extends Handler {
    protected void doHandle(Request request) {
        System.out.println("权限校验通过: " + request.getData());
    }
}

// 具体处理者:业务处理
class BusinessHandler extends Handler {
    protected void doHandle(Request request) {
        System.out.println("执行业务逻辑: " + request.getData());
    }
}

// 客户端
class Client {
    public static void main(String[] args) {
        Handler log = new LogHandler();
        Handler auth = new AuthHandler();
        Handler biz = new BusinessHandler();
        log.setNext(auth).setNext(biz);
        log.handleRequest(new Request(1, "用户请求"));
        // 输出:记录日志 -> 权限校验通过 -> 执行业务逻辑
    }
}

关键点 :每个处理者都参与处理,请求一路传到链尾。如果某个处理者需要中断链(如权限校验失败),最直接的方式是在 doHandle 中抛异常,由调用方 catch 处理:

java 复制代码
// 不纯责任链中断示例:权限校验失败抛异常
class AuthHandler extends Handler {
    protected void doHandle(Request request) {
        if (request.getLevel() < 0) {
            throw new RuntimeException("权限不足,请求被拒绝: " + request.getData());
        }
        System.out.println("权限校验通过: " + request.getData());
    }
}

抛异常是最常见的中断方式--不纯责任链通常运行在框架容器中(如 Servlet Filter),异常会被容器统一捕获并转换为错误响应。

函数式实现

Java 8 之后,处理者可以是一个 Function,靠 andThen 组合成链,不需要抽象类。这是不纯责任链的现代写法,每个处理者都对请求做一点处理再往下传,andThen 天然就是"先做自己的、再交给下一个"。

java 复制代码
import java.util.function.Function;

class FunctionalChain {
    public static void main(String[] args) {
        // 每个处理者是一个 Function:处理输入,返回输出
        Function<String, String> log = req -> {
            System.out.println("记录日志: " + req);
            return req;
        };
        Function<String, String> auth = req -> {
            System.out.println("权限校验: " + req);
            return req;
        };
        Function<String, String> biz = req -> {
            System.out.println("业务处理: " + req);
            return req;
        };

        // andThen 组合成链:log -> auth -> biz
        Function<String, String> chain = log.andThen(auth).andThen(biz);
        chain.apply("用户请求");
        // 输出:记录日志 -> 权限校验 -> 业务处理
    }
}

关键点Function.andThen 把多个处理者串成"先做自己的、再传下一个",等价于不纯责任链的"先 doHandlenext"。无需抽象类、无需 setNext,链就是函数的组合,写法极简。但中断不便:andThen 链中某个处理者想中断,只能抛异常或返回特殊标记,没有 chain.doFilter 那样"不调用即中断"的语义。

引入一个场景:快递流水线。打包、贴标、质检三道工序依次过,每道工序给包裹做一点加工就传给下一道,这正是 andThen 的"接力加工"。如果某道工序发现包裹破损,抛异常即可让整条流水线停下。

💡 何时用函数式 :处理者逻辑简单、无状态、且是"都参与处理"的不纯链时,优先用 andThen;一旦需要"按条件中断""共享上下文""运行时增删处理者",就回到抽象类方案,函数式胜在简洁,抽象类胜在控制力。

如何选择

维度 纯责任链 不纯责任链
处理方式 只有一个处理者处理请求 每个处理者都参与处理
传递时机 处理不了才传递 处理完自己的部分后继续传递
链中断 处理后自动停止 需要显式中断(异常/标志位)
典型场景 审批流程、异常处理 过滤器链、拦截器链

简单记忆审批用纯链,过滤用不纯链。纯责任链关注"谁来处理",不纯责任链关注"每个环节做什么"。

推荐:业务审批场景用纯责任链(一个请求只该一个人批);框架级的请求预处理用不纯责任链(每个切面都要过一遍)。大多数 Web 框架(Filter、Interceptor)都是不纯责任链。

总结

本质:把请求的发送者和接收者解耦,多个处理者串成链,请求沿链传递直到被处理。

什么时候用

  • 请求需要经过多个对象处理,但具体由谁处理、按什么顺序在编译时不确定
  • 想动态增删处理者或调整链的顺序,不想改动已有代码
  • 处理同一请求的方式可按优先级排列,逐个尝试

什么时候不用

  • 处理者之间有严格的固定顺序且不会变化,直接硬编码更简单
  • 只有一个处理者,没必要引入链
  • 处理者之间需要共享大量上下文,链式传递信息不方便

⚠️ 缺点与调试技巧

  • 性能损耗:链越长,请求要经过的节点越多,调用栈越深,长链在高频场景不可忽视
  • 调试困难:请求被默默转发,出错时不易确定是哪个处理者接收了请求,链越深越难追踪
  • 循环引用风险setNext 拼接错误可能形成环,请求在环里无限递归直至栈溢出
  • 调试建议 :在每个处理者入口加一行日志(处理者名加请求关键字),请求流向一目了然;链构建完成后遍历校验,确保无环(如用 Set 记录已访问节点,遇重复即抛异常)

简单记忆:发送者只管丢请求,处理者排队接力传;批不了就往后送,纯链一个办、不纯链一起干。

相似模式区分

总览

模式 接口关系 核心意图 典型场景
责任链 处理者持有下一个处理者的引用 沿链寻找处理者,请求被一个或多个处理者接收 审批流程、过滤器链
装饰 装饰者持有被装饰对象,实现相同接口 逐层增强对象功能 I/O 流包装、UI 组件增强
策略 策略之间独立,由 Context 持有和切换 一个上下文选择一种策略执行 支付方式、排序算法
命令 命令封装请求,调用者持有命令 把请求对象化,支持撤销、排队、记录 任务队列、宏命令、撤销重做

简单记忆:责任链找"谁来办",装饰模式层层"加 buff",策略模式"一次选一个",命令模式把请求"打包发"。

责任链 vs 装饰模式

两者都用链式结构,但意图完全不同:

维度 责任链模式 装饰模式
核心意图 沿链寻找处理者,请求被一个处理者接收 逐层增强对象功能,每个装饰者都参与处理
结构差异 处理者持有下一个处理者的引用 装饰者持有被装饰对象的引用,实现相同接口
关注点 请求的路由和分发 对象功能的动态扩展
典型场景 审批流程、过滤器链 I/O 流包装、UI 组件增强

逐步区分法

  • 如果请求只需要一个处理者接收 -> 选责任链
  • 如果每个环节都需要参与处理且增强同一个对象 -> 选装饰模式

简单记忆口诀:"责任链找谁来办,装饰模式层层加"。

推荐:两者都能用但意图不同--需要"选一个处理者"用责任链,需要"层层增强"用装饰模式。Servlet Filter 表面像装饰模式(每个都处理),但本质是责任链(各管各的切面,不是增强同一个对象)。

责任链 vs 策略模式

两者都用于选择处理方式,但选择机制不同:

维度 责任链模式 策略模式
核心意图 多个处理者沿链传递,动态寻找处理者 一个上下文选择一种策略执行
结构差异 处理者之间有链式连接(next 引用) 策略之间独立,由 Context 持有和切换
关注点 处理者的动态组合和传递 算法的可替换性
典型场景 审批流程、过滤器链、异常处理 支付方式选择、排序算法切换、折扣策略

逐步区分法

  • 如果处理者需要按顺序逐个判断能否处理 -> 选责任链
  • 如果可以直接确定用哪种策略 -> 选策略模式

简单记忆口诀:"责任链逐个试,策略模式一次选"。

推荐:能直接确定用哪个处理者就用策略模式(更简单高效),只有处理者不确定或需要逐个尝试时才用责任链。策略是"知道选谁",责任链是"不知道谁行,挨个试"。

责任链 vs 命令模式

两者都能把请求和处理分开,但关注点不同:命令模式把请求封装成对象,关注"请求的对象化、可撤销、可排队";责任链关注"多个处理者如何串联传递"。

维度 责任链模式 命令模式
核心意图 沿链寻找处理者,请求被一个或多个处理者接收 把请求封装成对象,使请求的发送者和执行者解耦
结构差异 处理者持有下一个处理者的引用 命令对象持有接收者,调用者持有命令
关注点 处理者的串联与传递 请求的对象化、撤销、排队、日志
典型场景 审批流程、过滤器链 任务队列、宏命令、撤销重做

逐步区分法

  • 如果目的是让多个处理者接力处理 -> 选责任链
  • 如果目的是把请求打包成对象以便撤销、排队、记录 -> 选命令模式

简单记忆口诀:"责任链接力传,命令模式打包发"。

推荐:两者常组合使用,把每个命令对象当作责任链上的一个处理者,就能拼出可配置的审批流:命令模式负责"把审批动作对象化",责任链负责"按权限层级串起来传"。单独用时,要"传给谁不确定"用责任链,要"请求可撤销可排队"用命令模式。

练习题目

跨部门采购审批

题目描述:某公司不同部门的采购审批链不同(例如 IT 部门没有经理层,销售部门没有组长层)。请使用责任链模式,根据输入动态构建审批链,处理采购申请。审批人及金额上限:

  • TeamLeader(组长):≤ 1000 元
  • Manager(经理):≤ 5000 元
  • Director(总监):≤ 20000 元

如果金额超过审批链末端处理人的上限,则由末端处理人否决。

输入描述 :第一行是审批链配置,由审批人名称组成,用空格分隔,按顺序执行。名称为 TeamLeader、Manager、Director,至少包含一个,顺序任意,可缺少。第二行是一个整数 N(1 ≤ N ≤ 100),表示采购申请数量。接下来的 N 行,每行格式为 申请人 金额

输出描述 :对于每个采购请求,输出一行:批准输出 申请人 Approved by 审批人.,否决输出 申请人 Denied by 审批人.(审批人为链中最后一个处理者)。

输入示例

yaml 复制代码
TeamLeader Manager Director
4
Alice 500
Bob 3000
Charlie 15000
David 25000

输出示例

csharp 复制代码
Alice Approved by TeamLeader.
Bob Approved by Manager.
Charlie Approved by Director.
David Denied by Director.

解题思路 :题目是纯责任链的典型应用--审批链可由输入动态拼接,正是责任链"运行时增删处理者、调整顺序"的强项。每种审批人是一个 ConcreteHandler,持有金额上限判断逻辑;客户端根据输入动态构建链,将采购请求发给链首。请求沿链传递--金额在当前审批人上限内则批准,否则传给下一个;到达链尾仍超限则否决。去掉责任链,客户端就得为每种审批链组合写一份硬编码的 if-else,部门一多组合爆炸。

java 复制代码
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String line = sc.nextLine();
        String[] strs = line.split(" ");

        Handler[] handlers = new Handler[strs.length];
        for (int i = 0; i < strs.length; i++) {
            handlers[i] = createHandler(strs[i]);
        }

        // 动态构建责任链
        for (int i = 0; i < handlers.length - 1; i++) {
            handlers[i].setNextHandler(handlers[i + 1]);
        }

        int n = sc.nextInt();
        while (n-- > 0) {
            String name = sc.next();
            int amount = sc.nextInt();
            handlers[0].handle(new Request(name, amount));
        }
    }

    static Handler createHandler(String type) {
        switch (type) {
            case "TeamLeader": return new TeamLeader();
            case "Manager":    return new Manager();
            default:           return new Director();
        }
    }
}

interface Handler {
    public void handle(Request request);
    public void setNextHandler(Handler handler);
}

class Request {
    private String name;
    private int amount;
    public Request(String name, int amount) {
        this.name = name;
        this.amount = amount;
    }
    public String getName() { return name; }
    public int getAmount() { return amount; }
}

class TeamLeader implements Handler {
    private Handler next;
    public void setNextHandler(Handler handler) { this.next = handler; }
    public void handle(Request request) {
        if (request.getAmount() <= 1000) {
            System.out.println(request.getName() + " Approved by TeamLeader.");
        } else if (next != null) {
            next.handle(request);
        } else {
            System.out.println(request.getName() + " Denied by TeamLeader.");
        }
    }
}

class Manager implements Handler {
    private Handler next;
    public void setNextHandler(Handler handler) { this.next = handler; }
    public void handle(Request request) {
        if (request.getAmount() <= 5000) {
            System.out.println(request.getName() + " Approved by Manager.");
        } else if (next != null) {
            next.handle(request);
        } else {
            System.out.println(request.getName() + " Denied by Manager.");
        }
    }
}

class Director implements Handler {
    private Handler next;
    public void setNextHandler(Handler handler) { this.next = handler; }
    public void handle(Request request) {
        if (request.getAmount() <= 20000) {
            System.out.println(request.getName() + " Approved by Director.");
        } else if (next != null) {
            next.handle(request);
        } else {
            System.out.println(request.getName() + " Denied by Director.");
        }
    }
}

扩展:实际项目中的责任链模式

Servlet Filter Chain

Java Servlet 的过滤器链是责任链最经典的应用。每个 Filter 在请求到达 Servlet 之前和响应返回之后各执行一段逻辑,如编码转换、权限校验、日志记录。FilterChain.doFilter() 是责任链的核心--每个 Filter 调用它把请求传给下一个 Filter,不调用就中断链。Filter 的执行顺序由 web.xml 里的 <filter-mapping> 顺序决定,这是典型的不纯责任链,每个 Filter 都参与处理。

java 复制代码
public class AuthFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            ((HttpServletResponse) response).sendError(401, "未授权");
            return;                          // 不调用 chain.doFilter,中断链
        }
        chain.doFilter(request, response);   // 继续传递
    }
}

Spring Cloud Gateway Filter

Spring Cloud Gateway 的过滤器链是云原生网关里责任链的典型应用,与 Servlet Filter 一脉相承但更现代化。请求进网关后依次穿过一串 GatewayFilterGatewayFilterChain.filter(exchange) 把请求传给下一个,不调用就中断链。Gateway 区分 GlobalFilter(全局生效,如鉴权、限流)和 GatewayFilter(通过路由配置绑定,如改写路径),这是典型的不纯责任链,每个过滤器都参与处理。

java 复制代码
@Component
public class AuthFilter implements GlobalFilter {
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();   // 不调 chain.filter,中断链
        }
        return chain.filter(exchange);                     // 继续传递
    }
}

Spring HandlerInterceptor

Spring MVC 的拦截器链是责任链在框架层的应用,在 Controller 处理请求前后执行通用逻辑。preHandle 返回 true 继续传递,返回 false 中断链。拦截器有三个钩子:preHandle(Controller 前)、postHandle(Controller 后视图渲染前)、afterCompletion(请求完成后清理资源)。与 Filter 不同,Interceptor 基于 Spring MVC,能拿到 Handler 信息,更贴近业务层。

java 复制代码
@Component
public class LoginInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null) {
            response.setStatus(401);
            return false;                    // 中断链
        }
        return true;                         // 继续传递
    }
}

OkHttp Interceptor

OkHttp 的拦截器机制是责任链在网络请求层的应用,每个拦截器能查看、修改请求和响应。chain.proceed(request) 把请求传给下一个拦截器,拦截器可以在 proceed 前改请求、在 proceed 后改响应--形成"请求从外到内、响应从内到外"的双向处理。OkHttp 区分 addInterceptor(应用拦截器,只调一次)和 addNetworkInterceptor(网络拦截器,重定向时多次调用)。

java 复制代码
public class HeaderInterceptor implements Interceptor {
    public Response intercept(Chain chain) throws IOException {
        Request modified = chain.request().newBuilder()
            .addHeader("Authorization", "Bearer token")
            .build();
        return chain.proceed(modified);      // 传递给下一个拦截器
    }
}

Spring Security Filter Chain

Spring Security 通过一串过滤器实现安全控制,每个 Filter 专管一个安全切面--CSRF、认证、授权、异常翻译各司其职。SecurityFilterChain 默认包含十几个 Filter,自定义 Filter 通过 addFilterBefore/addFilterAfter 插到指定位置,不用关心其他 Filter 的存在。遇到 JWT 认证时,自定义一个解析 token、塞入 SecurityContext 的 Filter 挂进链里即可。

java 复制代码
public class JwtAuthFilter extends OncePerRequestFilter {
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = resolveToken(request);
        if (token != null && tokenProvider.validateToken(token)) {
            Authentication auth = tokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(request, response);  // 继续传递
    }
}

MyBatis Plugin

MyBatis 的插件机制本质是一条由动态代理层层包裹出的责任链。InterceptorChain.pluginAll() 遍历所有插件,逐个用 Plugin.wrap() 生成代理包裹目标对象,四大核心对象(ExecutorStatementHandlerParameterHandlerResultSetHandler)都能被拦截。插件用 @Intercepts 注解声明要拦截哪个接口的哪个方法,Interceptor.intercept() 拦截后调 invocation.proceed() 把调用传给下一层代理。

java 复制代码
@Intercepts({
    @Signature(type = Executor.class, method = "query",
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
        // 拦截查询,改写 SQL 加分页
        return invocation.proceed();   // 传给下一层代理(最内层是真实 Executor)
    }
}

⚠️ MyBatis 插件结构上更像装饰模式:层层代理增强同一个目标对象,每个插件都参与。但它又具备责任链特征,多个插件按注册顺序串联,可中途拦截、可 proceed 放行。因此多数资料把它归为责任链,实际是责任链与装饰模式的混合体。

Netty ChannelPipeline

Netty 的 ChannelPipeline 是责任链在网络框架中的高级应用,每个 ChannelHandler 处理入站或出站事件。它的特殊之处是双向链 --入站事件(读数据)从 head 向 tail 传播,出站事件(写数据)从 tail 向 head 传播。ChannelInboundHandler 管入站,ChannelOutboundHandler 管出站,每个 Handler 调用 ctx.fireChannelRead(msg) 把事件传给下一个。编解码、心跳、业务处理全都挂在同一条 pipeline 上。

java 复制代码
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new StringDecoder());   // 入站:解码
pipeline.addLast("encoder", new StringEncoder());   // 出站:编码
pipeline.addLast("handler", businessHandler);       // 入站:业务处理

异常处理链

异常沿调用栈传播,本质是一条隐式的责任链--每层 catch 决定处理还是继续抛出。能处理的层就地消化,处理不了的就 throw 给上层,直到遇到能处理的层或到达顶层导致程序中止。这也是纯责任链的特征:请求(异常)被一个处理者接收后不再传递。

java 复制代码
try {
    doBusiness();                              // 业务调用
} catch (BusinessException e) {
    log.warn("业务异常,已处理", e);             // 本层能处理,消化掉
} catch (SQLException e) {
    throw new SystemException(e);               // 本层处理不了,转换后抛给上层
}

技术交流 & 更多原创内容,关注公众号:咖啡八杯

相关推荐
吃饱了得干活1 小时前
从零设计一个高并发点赞系统:缓存+MQ异步持久化实战
java·架构
禅思院1 小时前
AI对话前端从入门到崩溃:一个长对话引发的五层优化战争【六】
前端·架构·前端框架
Jiude1 小时前
AI 产品的几种付费方式
后端·架构·openai
江畔柳前堤2 小时前
Go04-控制结构
大数据·人工智能·面试·职场和发展·go·业界资讯
影视飓风TIM2 小时前
Linux基础入门笔记:命令、内核架构与压缩传输全梳理
linux·笔记·架构
触底反弹2 小时前
💥 暴力解 LeetCode 导致 OOM:10000 行数据撑爆 V8 堆内存的完整排查
javascript·算法·面试
wdfk_prog9 小时前
嵌入式面试真题第 10 题:高优化等级下共享状态可见性、内存模型与系统级同步设计
java·linux·开发语言·面试·职场和发展·架构·c
2501_9419820510 小时前
企业微信二次开发:私域数据基石——三大核心基础能力接口实战
windows·架构·bootstrap·企业微信