新人笔记---责任链模式

一:为什么要引入责任链模式

1、先理解:责任链模式要解决什么问题?

想象一个常见场景:用户提交了一个订单,系统需要依次做这些校验:

  1. 检查用户是否登录
  2. 检查订单金额是否合法
  3. 检查库存是否充足
  4. 检查用户账户余额是否足够

如果不用责任链模式,你可能会写出这样的代码:

java 复制代码
public void checkOrder(Order order) {
    // 1. 检查登录
    if (!userIsLogin()) {
        throw new Exception("用户未登录");
    }
    // 2. 检查金额
    if (order.getAmount() <= 0) {
        throw new Exception("订单金额不合法");
    }
    // 3. 检查库存
    if (!stockIsEnough(order)) {
        throw new Exception("库存不足");
    }
    // 4. 检查余额
    if (!balanceIsEnough(order)) {
        throw new Exception("余额不足");
    }
}

这种写法的问题很明显:

  • 耦合度极高:所有校验逻辑挤在一个方法里,改一个校验规则就要动整个方法;
  • 扩展性差:如果要新增 "检查优惠券是否有效",必须修改原有代码;
  • 职责不清晰:一个方法承担了多个校验责任,违反 "单一职责原则";
  • 灵活性低:无法动态调整校验顺序(比如想先查库存再查金额)。

责任链模式的核心思路是:把每个校验逻辑拆成独立的 "处理者",然后把这些处理者连成一条链,请求(订单校验)沿着链传递,直到某个处理者能处理它,或者整条链处理完。

2、引入责任链模式的核心原因(优势)

(1). 解耦:请求发送者和接收者分离

请求的发起者(比如提交订单的代码)不需要知道谁会处理这个请求,也不需要知道处理的顺序 ------ 它只需要把请求丢给责任链的 "头节点" 即可。比如订单校验的发起方,不需要知道是 "登录校验器" 先处理,还是 "库存校验器" 先处理,极大降低了模块间的耦合。

(2). 增强扩展性:新增 / 删除处理者更灵活

如果要新增 "优惠券校验",只需要新建一个CouponCheckHandler类(类似于给链表增加一个节点),然后把它加入责任链即可,完全不需要修改原有校验器的代码(符合 "开闭原则");如果要暂时关闭 "余额校验",只需要把这个处理者从链中移除,不影响其他逻辑。

(3). 动态调整处理顺序:灵活适配业务变化

比如某天业务要求 "先查库存,再查金额",只需要调整责任链中处理者的顺序,不需要修改任何处理者的内部逻辑。

(4). 单一职责:每个处理者只做一件事

每个校验器只负责自己的校验逻辑(比如LoginCheckHandler只检查登录),代码更清晰、易维护、易测试。

三、简单示例:用责任链重构订单校验

java 复制代码
// 1. 定义处理者抽象类
abstract class OrderCheckHandler {
    // 下一个处理者
    protected OrderCheckHandler nextHandler;

    // 设置下一个处理者(构建链)
    public void setNextHandler(OrderCheckHandler nextHandler) {
        this.nextHandler = nextHandler;
    }

    // 抽象处理方法
    public abstract void handle(Order order) throws Exception;
}

// 2. 具体处理者:登录校验
class LoginCheckHandler extends OrderCheckHandler {
    @Override
    public void handle(Order order) throws Exception {
        if (!userIsLogin()) {
            throw new Exception("用户未登录");
        }
        // 传递给下一个处理者
        if (nextHandler != null) {
            nextHandler.handle(order);
        }
    }

    private boolean userIsLogin() {
        // 实际登录校验逻辑
        return true;
    }
}

// 3. 具体处理者:金额校验
class AmountCheckHandler extends OrderCheckHandler {
    @Override
    public void handle(Order order) throws Exception {
        if (order.getAmount() <= 0) {
            throw new Exception("订单金额不合法");
        }
        if (nextHandler != null) {
            nextHandler.handle(order);
        }
    }
}

// 4. 客户端使用
public class Client {
    public static void main(String[] args) {
        // 构建责任链
        OrderCheckHandler loginHandler = new LoginCheckHandler();
        OrderCheckHandler amountHandler = new AmountCheckHandler();
        loginHandler.setNextHandler(amountHandler);

        // 发起请求(只需要调用链头)
        Order order = new Order(100.0);
        loginHandler.handle(order);
    }
}

// 订单实体
class Order {
    private double amount;
    public Order(double amount) {
        this.amount = amount;
    }
    public double getAmount() {
        return amount;
    }
}

这个示例中,新增 / 删除 / 调整校验逻辑,只需要操作处理者类和链的构建过程,完全不影响原有代码。

引入责任链模式(Chain of Responsibility),本质上是将"发起请求"与"处理请求"进行解耦 ,并将处理过程从"硬编码的串行"变为"动态的链式传递" 。我们把一个一个要处理的逻辑当成一个一个节点,比如登陆检验->支付校验->订单校验,依次从头到尾执行,串成一个链条,每个节点有自己的校验逻辑。如果我们想修改其中某个校验逻辑的先后顺序,比如修改成登陆校验->订单校验->支付校验,我们只需要动态修改链条的节点顺序即可,类似于链表中节点交换顺序,而不是将所有校验逻辑"硬编码"在一个类中

二、责任链模式的核心定义

责任链模式 (Chain of Responsibility Pattern) 是一种行为型设计模式,核心思想是:将请求的处理者连成一条链,请求沿着这条链依次传递,直到有一个处理者能处理该请求,或整条链处理完毕为止

你可以把它想象成生活中的 "审批流程":员工请假 1 天→组长审批;请假 3 天→组长审批后再到部门经理;请假 7 天→组长→部门经理→总监→CEO。每个审批者(处理者)只负责自己权限内的请求**,处理不了就传递给下一个,直到遇到可以处理的审批者,请求发起者(员工)只需要把请假单交给第一个审批者,不用关心后续谁来批、批到哪一步。

1、责任链模式的核心结构

一个标准的责任链模式包含 3 个核心角色,用简单的类比和代码结构帮你理解(以上方例子为准):

角色 作用 类比(请假审批)
抽象处理者 定义处理请求的抽象方法,同时持有下一个处理者的引用(用来构建链) 抽象的 "审批者" 类
具体处理者 实现抽象处理方法,判断自己能否处理请求:能则处理,不能则传递给下一个 组长、部门经理、总监
客户端 创建具体处理者,构建责任链,并发起请求(只调用链的第一个处理者) 提交请假单的员工

2、示例:请假审批

java 复制代码
// 1. 抽象处理者:定义审批规则和下一个处理者
abstract class Approver {
    // 下一个审批者
    protected Approver nextApprover;
    // 审批者名称
    protected String name;

    public Approver(String name) {
        this.name = name;
    }

    // 设置下一个审批者(构建链)
    public void setNextApprover(Approver nextApprover) {
        this.nextApprover = nextApprover;
    }

    // 抽象审批方法(核心)
    public abstract void approve(int leaveDays);
}

// 2. 具体处理者1:组长(只能批≤1天)
class TeamLeader extends Approver {
    public TeamLeader(String name) {
        super(name);
    }

    @Override
    public void approve(int leaveDays) {
        if (leaveDays <= 1) {
            System.out.println(name + "审批通过:请假" + leaveDays + "天");
        } else if (nextApprover != null) {
            // 处理不了,传给下一个
            System.out.println(name + "无权审批,转交给" + nextApprover.name);
            nextApprover.approve(leaveDays);
        } else {
            System.out.println("无人审批,请假失败");
        }
    }
}

// 3. 具体处理者2:部门经理(只能批≤3天)
class DepartmentManager extends Approver {
    public DepartmentManager(String name) {
        super(name);
    }

    @Override
    public void approve(int leaveDays) {
        if (leaveDays <= 3) {
            System.out.println(name + "审批通过:请假" + leaveDays + "天");
        } else if (nextApprover != null) {
            System.out.println(name + "无权审批,转交给" + nextApprover.name);
            nextApprover.approve(leaveDays);
        } else {
            System.out.println("无人审批,请假失败");
        }
    }
}

// 4. 客户端:构建链 + 发起请求
public class Client {
    public static void main(String[] args) {
        // 1. 创建具体处理者
        Approver teamLeader = new TeamLeader("组长");
        Approver deptManager = new DepartmentManager("部门经理");

        // 2. 构建责任链:组长 → 部门经理
        teamLeader.setNextApprover(deptManager);

        // 3. 发起请求(只调用链头)
        System.out.println("=== 请假1天 ===");
        teamLeader.approve(1); // 组长直接审批

        System.out.println("\n=== 请假2天 ===");
        teamLeader.approve(2); // 组长转交给部门经理审批
    }
}
运行结果:
java 复制代码
=== 请假1天 ===
组长审批通过:请假1天

=== 请假2天 ===
组长无权审批,转交给部门经理
部门经理审批通过:请假2天

流程图

3.责任链模式优点

1. 解耦:请求发起者与处理者完全分离

这是责任链模式最核心的特点。

  • 请求的发起者(客户端)只需要知道链的第一个处理者,不需要知道后续谁来处理、处理顺序是什么、每个处理者的具体逻辑。
  • 比如员工提交请假单,只需要交给组长,不用关心是组长批、经理批还是 CEO 批;订单校验的发起方,只需要把订单传给第一个校验器,不用知道后续的校验逻辑。
  • 这种解耦避免了 "请求发起者直接依赖所有处理者" 的硬编码问题,降低了模块间的耦合度。
2. 灵活性:动态调整处理链(新增 / 删除 / 排序)

责任链的结构是 "可配置" 的,这让业务适配变得极灵活:

  • 新增处理者 :比如请假流程要加 "HR 审批",只需新建HRApprover类,把它加入链中即可,无需修改原有审批者的代码(符合 "开闭原则");
  • 删除处理者:比如暂时关闭 "库存校验",只需把库存校验器从链中移除,不影响其他校验逻辑;
  • 调整顺序:比如订单校验想先查库存再查金额,只需调整处理者的链接顺序,不用改任何处理者的内部逻辑。
3. 单一职责:每个处理者只做一件事

每个具体处理者只负责自己的核心逻辑,不掺杂其他处理者的职责:

  • 组长只判断 "1 天内的假是否能批",部门经理只判断 "1-3 天的假是否能批",CEO 只判断 "3 天以上的假是否能批";
  • 登录校验器只检查登录状态,金额校验器只检查金额合法性;
  • 这种设计让代码逻辑清晰、易测试、易维护,符合 "单一职责原则"。
4. 请求的 "透明传递":处理者自主决定是否传递

请求沿链传递的过程是 "自主的":

  • 每个处理者判断自己能否处理当前请求:能处理则直接处理,不能则传递给下一个;
  • 比如请假 2 天,组长判断自己处理不了,自动传给部门经理,无需客户端干预;
  • 这种 "自主传递" 让处理逻辑更贴合业务规则,无需在客户端写多层if-else判断。

这里需要注意责任链的处理者都有自主权,当前处理者能否处理当前请求与处理完后是否要跳转到下一个处理者,这都是根据业务去定的。责任链中当前节点处理完请求后,是否交给下一个,完全由你(开发者)决定,不是固定规则。具体看下面的例子

  • 只处理自己的部分,不传递给下一个(比如 "请假 1 天,组长批完就结束,不用给经理");
  • 也可以处理完自己的部分,再传递给下一个(比如 "订单校验:登录校验通过后,继续做金额校验");
  • 唯一的固定规则是:是否传递,由当前节点的业务逻辑决定

下面我用两个最常见的场景,结合代码对比,帮你彻底理解这个点:

场景 1:"排他型" 处理(处理完就终止,不传递)

这是最常见的场景之一:请求只要被当前节点处理,就不再传递给下一个。典型例子:请假审批(1 天组长批、3 天经理批、7 天总监批)------ 谁能批,谁就处理,处理完就结束。

java 复制代码
// 抽象审批者(和之前一致)
abstract class Approver {
    protected Approver nextApprover;
    protected String name;
    public Approver(String name) { this.name = name; }
    public void setNextApprover(Approver next) { this.nextApprover = next; }
    public abstract void approve(int leaveDays);
}

// 组长(只能批≤1天,批完就终止)
class TeamLeader extends Approver {
    public TeamLeader(String name) { super(name); }

    @Override
    public void approve(int leaveDays) {
        if (leaveDays <= 1) {
            // ✅ 处理完,不传递给下一个
            System.out.println(name + "审批通过:请假" + leaveDays + "天(终止流程)");
        } else if (nextApprover != null) {
            // 处理不了,传递给下一个
            System.out.println(name + "无权审批,转交给" + nextApprover.name);
            nextApprover.approve(leaveDays);
        }
    }
}

// 部门经理(只能批≤3天,批完就终止)
class DeptManager extends Approver {
    public DeptManager(String name) { super(name); }

    @Override
    public void approve(int leaveDays) {
        if (leaveDays <= 3) {
            // ✅ 处理完,不传递给下一个
            System.out.println(name + "审批通过:请假" + leaveDays + "天(终止流程)");
        } else if (nextApprover != null) {
            System.out.println(name + "无权审批,转交给" + nextApprover.name);
            nextApprover.approve(leaveDays);
        }
    }
}

// 客户端测试
public class Client {
    public static void main(String[] args) {
        Approver leader = new TeamLeader("组长");
        Approver manager = new DeptManager("部门经理");
        leader.setNextApprover(manager);

        // 测试1:请假1天(组长处理,不传递)
        System.out.println("=== 请假1天 ===");
        leader.approve(1);

        // 测试2:请假2天(组长传递给经理,经理处理,不传递)
        System.out.println("\n=== 请假2天 ===");
        leader.approve(2);
    }
}

运行结果

java 复制代码
=== 请假1天 ===
组长审批通过:请假1天(终止流程)

=== 请假2天 ===
组长无权审批,转交给部门经理
部门经理审批通过:请假2天(终止流程)

这个场景中,只要当前节点能处理,就不会交给下一个------ 这是责任链最经典的 "排他型" 用法。

场景 2:"叠加型" 处理(处理完继续传递给下一个)

另一种常见场景:当前节点处理完自己的逻辑后,继续把请求传递给下一个节点,让所有节点都处理一遍。典型例子:订单提交后的 "全量校验"(登录→金额→库存→余额,每一步都要做,一个都不能少)。

java 复制代码
// 抽象校验器
abstract class OrderCheckHandler {
    protected OrderCheckHandler next;
    public void setNext(OrderCheckHandler next) { this.next = next; }
    public abstract void check(Order order);
}

// 登录校验器(处理完继续传递)
class LoginCheck extends OrderCheckHandler {
    @Override
    public void check(Order order) {
        System.out.println("1. 登录校验:通过");
        // ✅ 处理完自己的,继续传递给下一个
        if (next != null) {
            next.check(order);
        }
    }
}

// 金额校验器(处理完继续传递)
class AmountCheck extends OrderCheckHandler {
    @Override
    public void check(Order order) {
        System.out.println("2. 金额校验:通过(金额=" + order.getAmount() + ")");
        if (next != null) {
            next.check(order);
        }
    }
}

// 订单实体
class Order {
    private double amount;
    public Order(double amount) { this.amount = amount; }
    public double getAmount() { return amount; }
}

// 客户端测试
public class Client {
    public static void main(String[] args) {
        OrderCheckHandler login = new LoginCheck();
        OrderCheckHandler amount = new AmountCheck();
        login.setNext(amount);

        // 发起请求:登录校验→金额校验(两个都处理)
        System.out.println("=== 订单校验 ===");
        login.check(new Order(100.0));
    }
}

运行结果

java 复制代码
=== 订单校验 ===
1. 登录校验:通过
2. 金额校验:通过(金额=100.0)

这个场景中,每个节点都处理自己的逻辑,然后主动传递给下一个------ 所有节点都会参与处理,直到链尾。

关键总结:"是否传递" 的核心逻辑
场景类型 处理逻辑 是否传递给下一个 典型例子
排他型(终止) 当前节点能处理请求 不传递 请假审批
叠加型(继续) 当前节点处理完自己的逻辑 传递 订单全量校验
混合型(灵活) 部分场景传递,部分场景终止 看业务规则 日志级别处理

4、设计注意点(易踩坑的特点)

1. 可能存在 "请求无人处理" 的情况

如果整条链的所有处理者都无法处理请求,且没有兜底逻辑,请求会 "石沉大海":

  • 比如请假 10 天,但链中只有组长和部门经理(最大批 3 天),若没有 "无人审批" 的兜底,请求会传递到链尾后无响应;
  • 因此设计时必须加兜底逻辑(比如示例中 "无人审批,请假失败"),避免请求丢失。
2. 链过长可能导致性能问题或调试复杂

如果责任链的处理者过多(比如 10 个以上),会带来两个问题:

  • 性能:请求需要依次经过所有前置处理者,增加了调用链路的长度;
  • 调试:出问题时需要逐一环查 "哪个处理者出了问题",定位成本升高;
  • 建议:责任链的长度控制在合理范围,或给每个处理者加日志标识(比如 "[组长] 传递请求给部门经理")。
3. 处理者的顺序可能影响最终结果

责任链是 "顺序执行" 的,顺序错误会导致业务逻辑出错:

  • 比如订单校验先查余额再查库存,若库存不足但余额足够,会先扣余额再发现库存不足,导致业务异常;
  • 因此设计时要严格梳理业务规则,确保处理者的顺序符合业务逻辑。

特点总结示例(对比非责任链模式)

特点 责任链模式 非责任链模式(硬编码if-else
解耦程度 发起者与处理者完全解耦 发起者依赖所有处理逻辑,耦合极高
扩展性 新增处理者无需改原有代码 新增逻辑必须修改原有if-else
顺序调整 只需调整链的链接顺序 必须修改if-else的判断顺序
职责清晰度 每个处理者只做一件事 所有逻辑挤在一个方法,职责混乱
调试复杂度 链过长时调试稍复杂 逻辑集中,调试简单但维护难

四、责任链模式写法

一、核心写法(基础版)

这是责任链模式的标准实现,分为 3 个步骤,适配绝大多数基础场景(如请假审批、订单校验)。

步骤 1:定义抽象处理者(核心规范)

抽象类 / 接口定义处理方法和下一个处理者的引用,是责任链的 "骨架"。

java 复制代码
// 抽象处理者(推荐用抽象类,方便封装公共逻辑)
public abstract class Handler {
    // 下一个处理者(核心:构建链条的关键)
    protected Handler nextHandler;

    // 设置下一个处理者(链式调用,方便客户端构建)
    public Handler setNext(Handler nextHandler) {
        this.nextHandler = nextHandler;
        return nextHandler; // 链式返回,方便连续设置
    }

    // 抽象处理方法(子类必须实现)
    public abstract void handle(Request request);

    // 可选:公共工具方法(比如兜底逻辑)
    protected void fallback(Request request) {
        System.out.println("【兜底】无人处理请求:" + request.getContent());
    }
}
步骤 2:实现具体处理者(业务逻辑)

每个具体处理者只关注自己的职责,判断是否处理、是否传递给下一个。

java 复制代码
// 具体处理者 1:初级处理者(处理类型 1 的请求)
public class FirstHandler extends Handler {
    @Override
    public void handle(Request request) {
        if (request.getType() == 1) {
            // 处理自己负责的请求,处理完可选择是否传递
            System.out.println("FirstHandler 处理请求:" + request.getContent());
            // 场景1:排他型(处理完终止,不传递)
            // 若要传递,取消下面注释:
            // if (nextHandler != null) {
            //     nextHandler.handle(request);
            // }
        } else {
            // 处理不了,传递给下一个(或兜底)
            if (nextHandler != null) {
                nextHandler.handle(request);
            } else {
                fallback(request); // 无后续处理者,兜底
            }
        }
    }
}

// 具体处理者 2:次级处理者(处理类型 2 的请求)
public class SecondHandler extends Handler {
    @Override
    public void handle(Request request) {
        if (request.getType() == 2) {
            System.out.println("SecondHandler 处理请求:" + request.getContent());
            // 场景2:叠加型(处理完继续传递)
            if (nextHandler != null) {
                nextHandler.handle(request);
            }
        } else {
            if (nextHandler != null) {
                nextHandler.handle(request);
            } else {
                fallback(request);
            }
        }
    }
}

// 请求实体(封装需要处理的参数)
public class Request {
    private int type; // 请求类型(1=初级,2=次级)
    private String content; // 请求内容

    // 构造器 + getter/setter
    public Request(int type, String content) {
        this.type = type;
        this.content = content;
    }

    public int getType() { return type; }
    public String getContent() { return content; }
}
步骤 3:客户端构建链条并调用

客户端只需创建处理者、拼接链条、调用链头,无需关心内部处理逻辑。

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 1. 创建具体处理者
        Handler firstHandler = new FirstHandler();
        Handler secondHandler = new SecondHandler();

        // 2. 构建链条:firstHandler → secondHandler(链式设置更简洁)
        firstHandler.setNext(secondHandler);

        // 3. 发起请求(只调用链头)
        System.out.println("=== 测试请求类型1(排他型)===");
        firstHandler.handle(new Request(1, "请假1天"));

        System.out.println("\n=== 测试请求类型2(叠加型)===");
        firstHandler.handle(new Request(2, "请假3天"));

        System.out.println("\n=== 测试无匹配处理者(兜底)===");
        firstHandler.handle(new Request(3, "请假7天"));
    }
}

运行结果

java 复制代码
=== 测试请求类型1(排他型)===
FirstHandler 处理请求:请假1天

=== 测试请求类型2(叠加型)===
SecondHandler 处理请求:请假3天

=== 测试无匹配处理者(兜底)===
【兜底】无人处理请求:请假7天
二、进阶写法(构建器模式优化)

当处理者数量多、链条复杂时,用「构建器模式」封装链条构建逻辑,让客户端更简洁。

步骤 1:新增责任链构建器
java 复制代码
public class HandlerChainBuilder {
    // 链头
    private Handler head;
    // 链尾(方便追加处理者)
    private Handler tail;

    // 添加处理者到链条
    public HandlerChainBuilder addHandler(Handler handler) {
        if (head == null) {
            // 第一个处理者,头尾都指向它
            head = handler;
            tail = handler;
        } else {
            // 追加到链尾,更新尾节点
            tail.setNext(handler);
            tail = handler;
        }
        return this; // 链式调用
    }

    // 获取构建好的链头
    public Handler build() {
        return head;
    }
}
步骤 2:客户端简化调用
java 复制代码
public class Client {
    public static void main(String[] args) {
        // 用构建器快速构建链条,无需手动setNext
        Handler chain = new HandlerChainBuilder()
                .addHandler(new FirstHandler())
                .addHandler(new SecondHandler())
                .build();

        // 调用逻辑不变
        chain.handle(new Request(1, "请假1天"));
    }
}

下面解释一下构造器写法与基础写法的区别

一、先回答:为什么要写构建器版?(解决基础版的痛点)

基础版中,你需要手动调用 setNext() 拼接链条,比如:

java 复制代码
// 基础版拼接链条
Handler first = new FirstHandler();
Handler second = new SecondHandler();
Handler third = new ThirdHandler();
// 手动设置:first → second → third
first.setNext(second);
second.setNext(third);

这种写法在处理者数量少(2-3 个)时没问题,但处理者变多(比如 5 个以上)会暴露 3 个核心问题:

1. 代码冗余且易出错
  • 每加一个处理者,就要多写一行 xxx.setNext(yyy)
  • 一旦拼错顺序(比如把 second.setNext(third) 写成 first.setNext(third)),整个链条逻辑就乱了,且不易排查。
2. 构建逻辑和业务代码耦合
  • 客户端既要关心 "怎么处理请求",又要关心 "怎么拼链条",违反「单一职责原则」;
  • 如果多个地方需要构建相同的链条,会重复写拼接代码,维护成本高。
3. 扩展性差
  • 新增 / 删除处理者时,要逐行修改 setNext() 代码,容易漏改或改错;
  • 无法快速实现 "动态调整链条顺序"(比如临时把某个处理者移到前面)。

构建器模式优化版 ,就是把「链条构建逻辑」从客户端抽离出来,封装到 HandlerChainBuilder 中,解决以上所有问题。

二、构建器版 vs 基础版:核心区别对比

表格

维度 基础版 构建器版
链条构建逻辑 客户端手动调用 setNext() 拼接 封装在 HandlerChainBuilder
代码简洁度 处理者越多,setNext() 越多 无论多少处理者,都是 addHandler() 链式调用
出错概率 高(易拼错顺序、漏写) 低(构建器内部统一处理顺序)
职责分离 客户端兼顾 "构建 + 调用" 客户端只负责 "调用",构建器负责 "构建"
扩展性 新增处理者需修改多处 setNext() 新增处理者只需加一行 addHandler()
举个直观例子:5 个处理者的场景

假设需要构建链条:First → Second → Third → Fourth → Fifth

  • 基础版 :需要写 4 行 setNext(),代码冗长且易错:

    java 复制代码
    Handler first = new FirstHandler();
    Handler second = new SecondHandler();
    Handler third = new ThirdHandler();
    Handler fourth = new FourthHandler();
    Handler fifth = new FifthHandler();
    
    first.setNext(second);
    second.setNext(third);
    third.setNext(fourth);
    fourth.setNext(fifth); // 漏写这行,链条就断了
  • 构建器版 :只需链式调用 addHandler(),逻辑清晰且不易错

    java 复制代码
    Handler chain = new HandlerChainBuilder()
            .addHandler(new FirstHandler())
            .addHandler(new SecondHandler())
            .addHandler(new ThirdHandler())
            .addHandler(new FourthHandler())
            .addHandler(new FifthHandler())
            .build();
三、构建器版的核心设计思路(为什么这么写?)

HandlerChainBuilder 的代码看似简单,但每一行都针对基础版的痛点设计:

java 复制代码
public class HandlerChainBuilder {
    private Handler head; // 链头:最终返回给客户端调用
    private Handler tail; // 链尾:方便追加新处理者

    public HandlerChainBuilder addHandler(Handler handler) {
        if (head == null) {
            // 第一个处理者:头尾都指向它
            head = handler;
            tail = handler;
        } else {
            // 后续处理者:拼到链尾,更新尾节点
            tail.setNext(handler); 
            tail = handler;
        }
        return this; // 链式调用关键:返回自身,支持连续add
    }

    public Handler build() {
        return head; // 只返回链头,客户端无需关心尾节点
    }
}

核心设计点:

  1. headtail 简化拼接

    • head 记录链条的第一个节点(最终返回给客户端);
    • tail 记录链条的最后一个节点,新增处理者时只需拼到 tail 后,无需手动找前一个节点。
  2. 链式调用(return this

    • addHandler() 可以连续调用,代码更简洁(比如 addHandler(a).addHandler(b))。
  3. 封装构建细节

    • 客户端只需要 "添加处理者",无需关心 "怎么拼接",即使内部拼接逻辑变了(比如要按优先级排序),客户端代码也不用改。

五.Spring中责任链的使用

应用场景 核心接口/类 链的类型 说明
Servlet 过滤器 Filter / FilterChain 叠加型 请求/响应拦截,如认证、日志、压缩
Spring 拦截器 HandlerInterceptor 叠加型 Controller 层的前置/后置处理

在 Spring 生态中,过滤器(Filter)拦截器(Interceptor) 是实现请求预处理 / 后处理的核心机制,本质都是「责任链模式」的典型应用,但两者的底层实现、执行时机、适用场景有显著区别。下面我会从「核心区别」「代码示例」「使用场景」「选型建议」四个维度讲清楚,帮你彻底理解并正确使用。

一、核心区别(先记清本质差异)

维度 过滤器(Filter) 拦截器(Interceptor)
所属体系 Servlet 规范(Java EE),与 Spring 无关 Spring MVC 框架特有,依赖 Spring 上下文
执行时机 请求进入 Servlet 之前(早于 Spring 拦截器) 请求进入 Controller 之前(晚于 Filter)
拦截范围 所有请求(包括静态资源:JS/CSS/ 图片等) 仅拦截 Spring MVC 管理的请求(Controller 接口)
可操作对象 仅能操作 HttpServletRequest/HttpServletResponse 可操作 HandlerMethod、Spring 上下文、Bean 等
生命周期 由 Servlet 容器管理(与 Spring 容器独立) 由 Spring 容器管理(支持依赖注入)
拦截粒度 粗粒度(按 URL 路径拦截) 细粒度(可按 Controller / 方法 / 注解拦截)
核心注解 / 接口 @WebFilterFilter 接口 HandlerInterceptor 接口、@Configuration

二、代码示例

1. 过滤器(Filter)实现
步骤 1:定义过滤器(实现 Filter 接口)
java 复制代码
// 1. 自定义过滤器
@Component
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        System.out.println("【前置】记录请求日志");
        // 继续传递到下一个过滤器
        chain.doFilter(request, response);
        System.out.println("【后置】记录响应日志");
    }
}

@Component
public class AuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        System.out.println("【前置】验证用户身份");
        // 验证通过,继续传递
        chain.doFilter(request, response);
    }
}

// 2. 注册过滤器(控制顺序)
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new LoggingFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(1);  // 顺序:数字越小越先执行
        return registration;
    }
    
    @Bean
    public FilterRegistrationBean<AuthFilter> authFilter() {
        FilterRegistrationBean<AuthFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new AuthFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(2);
        return registration;
    }
}
java 复制代码
Request → LoggingFilter(前置) → AuthFilter(前置) → Controller → AuthFilter(后置) → LoggingFilter(后置) → Response
2. 拦截器(Interceptor)实现
步骤 1:定义拦截器(实现 HandlerInterceptor 接口)
java 复制代码
// 拦截器由Spring管理,可直接注入Bean
@Component // 交给Spring管理
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService; // 直接注入,无需手动获取

    // 前置处理:Controller方法执行前(核心)
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 细粒度控制:仅拦截HandlerMethod(Controller方法)
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            String methodName = handlerMethod.getMethod().getName();
            System.out.println("拦截到Controller方法:" + methodName);

            // 2. 检查登录
            String token = request.getHeader("token");
            if (token == null || !userService.checkToken(token)) {
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write("{"code":401,"msg":"未登录"}");
                return false; // 拦截,不执行Controller方法
            }
        }
        return true; // 放行
    }

    // 后置处理:Controller方法执行后,视图渲染前
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Interceptor 后置处理:Controller方法已执行");
    }

    // 完成处理:视图渲染后(最终处理)
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor 完成处理:请求已全部处理");
    }
}
步骤 2:配置拦截器(注册到 Spring MVC)
java 复制代码
@Configuration // 配置类
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    // 注册拦截器并指定拦截路径
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有路径
                .excludePathPatterns("/login", "/static/**"); // 排除登录接口、静态资源
    }
}

三、责任链模式在 Filter/Interceptor 中的体现

Filter 和 Interceptor 本身就是责任链模式的经典实现:

1. 过滤器链(FilterChain)
  • FilterChain.doFilter() 是责任链的核心:当前 Filter 处理完后,调用 doFilter 传递给下一个 Filter;
  • 可配置多个 Filter,按 @Order(或注册顺序)执行;
java 复制代码
// 多个Filter示例:按Order排序
@WebFilter(urlPatterns = "/*")
@Order(1) // 第一个执行
public class LogFilter implements Filter { /* ... */ }

@WebFilter(urlPatterns = "/*")
@Order(2) // 第二个执行
public class LoginFilter implements Filter { /* ... */ }
2. 拦截器链(InterceptorChain)
  • Spring MVC 会将所有注册的 Interceptor 组成链条,按 addInterceptors 的注册顺序(或 @Order)执行;
  • 每个 Interceptor 的 preHandle 返回 true 才会传递给下一个 Interceptor;

解释一下拦截器的@order注解的揭发

直接给拦截器类加 @Order(Spring 4.3+ 支持)

Spring 4.3 及以上版本,直接给拦截器 Bean 加 @Order 注解,就能控制多个拦截器的执行顺序:

java 复制代码
// 拦截器1:Order=1(优先级高,先执行)
@Component
@Order(1) 
public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(...) {
        System.out.println("1. 日志拦截器(先执行)");
        return true;
    }
}

// 拦截器2:Order=2(优先级低,后执行)
@Component
@Order(2) 
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(...) {
        System.out.println("2. 登录拦截器(后执行)");
        return true;
    }
}

// 配置类:直接注册所有拦截器(无需指定顺序)
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private LogInterceptor logInterceptor;
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 无需关心add的顺序,@Order会自动排序
        registry.addInterceptor(loginInterceptor); 
        registry.addInterceptor(logInterceptor);
    }
}

关键注意点(避坑核心)

@Order 生效的前提:拦截器是 Spring 管理的 Bean

  • 只有当拦截器通过 @Component 交给 Spring 管理,或通过 @Bean 注册时,@Order 才会生效;
  • 如果是手动 new 拦截器(如 registry.addInterceptor(new LoginInterceptor())),且未加 @Order,则执行顺序由 addInterceptors 中的「注册顺序」决定。

四、适用场景与选型建议

1. 选过滤器(Filter)的场景
  • 需要拦截所有请求(包括静态资源、非 Spring 管理的接口);
  • 需要修改请求 / 响应的底层参数(如编码、请求体);
  • 实现跨域(CORS)、XSS 过滤、字符编码统一等全局功能;
2. 选拦截器(Interceptor)的场景
  • 仅拦截 Spring MVC 的 Controller 请求;
  • 需要细粒度控制(按 Controller / 方法 / 注解拦截);
  • 需要使用 Spring 容器中的 Bean(如 Service、Repository);
  • 实现登录校验、权限控制、接口日志、性能监控等业务逻辑;
3. 避坑指南
  • Filter 中注入 Spring Bean 需手动从上下文获取(因生命周期独立);
  • Interceptor 仅能拦截 DispatcherServlet 处理的请求,无法拦截直接访问的静态资源;
  • 多个 Filter/Interceptor 的执行顺序:Filter 按 @Order,Interceptor 按注册顺序(或 @Order)。

总结

Spring 中过滤器与拦截器的核心要点:

  1. 本质差异:Filter 属于 Servlet 规范(全局、底层),Interceptor 属于 Spring MVC(业务、细粒度);
  2. 责任链体现:两者均通过链条模式执行,支持多节点顺序处理,符合责任链 "解耦、灵活" 的核心优势;
  3. 选型原则:全局通用功能用 Filter,业务相关拦截用 Interceptor;
  4. 关键注意:Filter 注入 Spring Bean 需手动获取,Interceptor 可直接注入。
相关推荐
㳺三才人子3 小时前
初探 Flask
后端·python·flask·html
星栈独行3 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Java爱好狂.4 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易4 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
装不满的克莱因瓶4 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl5 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
excel6 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
卷毛的技术笔记6 小时前
Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”
java·人工智能·redis·后端·spring·ai·系统架构
IT_陈寒7 小时前
Java的Optional差点让我掉坑里,这几个坑你别踩
前端·人工智能·后端
子兮曰8 小时前
Harness 驾驭工程深度教程:从 AGENTS.md 到全链路 AI 编码基础设施
前端·后端·ai编程