【Java项目技术亮点】责任链模式实现审批流

写在前面:说实话,我见过太多人写审批流,上来就是一堆 if-else 嵌套。请假三天找组长,五天找经理,七天找 HR------代码里层层叠叠的判断,改一个规则要动三四个地方。这个坑我踩过,所以今天把责任链模式掰开揉碎讲清楚,看完你也能写出优雅的审批流。

文章目录

    • 一、为什么需要责任链模式?
      • [1.1 场景引入:请假审批的噩梦](#1.1 场景引入:请假审批的噩梦)
      • [1.2 生活类比:医院看病](#1.2 生活类比:医院看病)
      • [1.3 核心价值](#1.3 核心价值)
    • 二、责任链模式原理
      • [2.1 定义](#2.1 定义)
      • [2.2 角色构成](#2.2 角色构成)
      • [2.3 执行流程](#2.3 执行流程)
    • [三、纯 Java 实现责任链](#三、纯 Java 实现责任链)
      • [3.1 抽象处理者](#3.1 抽象处理者)
      • [3.2 具体处理者实现](#3.2 具体处理者实现)
      • [3.3 客户端使用](#3.3 客户端使用)
      • [3.4 动态构建责任链(从配置读取)](#3.4 动态构建责任链(从配置读取))
    • [四、配合 Spring 实现优雅的责任链](#四、配合 Spring 实现优雅的责任链)
      • [4.1 使用 @Order 注解排序](#4.1 使用 @Order 注解排序)
      • [4.2 自动组装责任链](#4.2 自动组装责任链)
      • [4.3 Service 层使用](#4.3 Service 层使用)
      • [4.4 更优雅的实现:利用 ApplicationContext](#4.4 更优雅的实现:利用 ApplicationContext)
    • [五、Flowable 工作流引擎简介](#五、Flowable 工作流引擎简介)
      • [5.1 Flowable 是什么?](#5.1 Flowable 是什么?)
      • [5.2 Flowable 的 BPMN 流程图设计](#5.2 Flowable 的 BPMN 流程图设计)
      • [5.3 Flowable 与 Spring Boot 整合](#5.3 Flowable 与 Spring Boot 整合)
    • [六、责任链 vs 策略模式 vs 装饰器模式对比](#六、责任链 vs 策略模式 vs 装饰器模式对比)
    • 七、踩坑指南
    • 八、问题与解答
    • 九、面试高频考点汇总
      • [考点 1:什么是责任链模式?说说它的优缺点。](#考点 1:什么是责任链模式?说说它的优缺点。)
      • [考点 2:你在项目中是怎么用责任链模式的?](#考点 2:你在项目中是怎么用责任链模式的?)
      • [考点 3:责任链模式和策略模式有什么区别?](#考点 3:责任链模式和策略模式有什么区别?)
      • [考点 4:如何避免责任链中的循环引用?](#考点 4:如何避免责任链中的循环引用?)
      • [考点 5:Spring 中哪些地方用到了责任链模式?](#考点 5:Spring 中哪些地方用到了责任链模式?)
    • 十、模拟面试官提问和参考答案
      • [场景题 1:设计一个电商订单的售后审批流](#场景题 1:设计一个电商订单的售后审批流)
      • [场景题 2:责任链如何实现"审批通过"和"审批驳回"两种结果?](#场景题 2:责任链如何实现"审批通过"和"审批驳回"两种结果?)
      • [场景题 3:如何实现责任链的"中断恢复"?](#场景题 3:如何实现责任链的"中断恢复"?)
      • [场景题 4:责任链模式如何与 AOP 结合做日志和权限校验?](#场景题 4:责任链模式如何与 AOP 结合做日志和权限校验?)
      • [场景题 5:如何设计一个支持"条件分支"的责任链?](#场景题 5:如何设计一个支持"条件分支"的责任链?)
    • 十一、互动话题
    • 十二、参考资料

一、为什么需要责任链模式?

1.1 场景引入:请假审批的噩梦

想象一个典型的请假审批流程:

复制代码
员工提交请假单 → 组长审批 → 部门经理审批 → HR 审批 → 结束

如果用 if-else 写,代码大概长这样:

java 复制代码
public void approve(LeaveRequest request) {
    if (request.getDays() <= 3) {
        // 组长审批
        groupLeader.approve(request);
    } else if (request.getDays() <= 7) {
        // 组长先看一眼,再交给经理
        groupLeader.approve(request);
        if (request.isGroupLeaderApproved()) {
            deptManager.approve(request);
        }
    } else {
        // 更复杂的嵌套...
    }
}

问题很明显:

  • 每加一个审批节点,代码就多一层嵌套
  • 审批规则变了,要改多处代码
  • 不同业务(请假、报销、采购)各自写一套,重复造轮子

1.2 生活类比:医院看病

去医院看病是什么流程?

复制代码
挂号 → 内科 → 化验 → 取药 → 回家

你不需要记住全部流程,到了挂号处,护士告诉你去内科;内科医生看完,告诉你去化验;化验完,告诉你去取药。每个环节只负责自己的事儿,处理完交给下一个环节。

责任链模式就是这个思路。

1.3 核心价值

价值点 说明
流程可配置 审批节点顺序可以动态调整,不用改代码
节点可插拔 新增或删除节点,其他节点不受影响
代码解耦 每个 Handler 只关心自己的逻辑
符合开闭原则 扩展新节点,不需要修改已有代码

二、责任链模式原理

2.1 定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

2.2 角色构成

复制代码
┌─────────────────────────────────────────┐
│              Client(客户端)              │
│         发送请求,不关心谁处理              │
└─────────────────┬───────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│      Handler(抽象处理者)                │
│  - setNext(Handler next)                │
│  - handle(Request request)              │
└─────────────────┬───────────────────────┘
                  │
    ┌─────────────┼─────────────┐
    ▼             ▼             ▼
┌────────┐   ┌────────┐   ┌────────┐
│HandlerA│ → │HandlerB│ → │HandlerC│
└────────┘   └────────┘   └────────┘

三个核心角色:

  1. Handler(抽象处理者):定义处理请求的接口,包含设置下一个处理者的方法
  2. ConcreteHandler(具体处理者):实现具体的处理逻辑,处理不了就交给下一个
  3. Client(客户端):创建责任链,并向链头发送请求

2.3 执行流程

复制代码
Client 发送请求
    │
    ▼
Handler1 能处理?→ 处理并结束
    │ 不能处理
    ▼
Handler2 能处理?→ 处理并结束
    │ 不能处理
    ▼
Handler3 能处理?→ 处理并结束
    │ 不能处理
    ▼
  请求未处理(或默认处理)

三、纯 Java 实现责任链

3.1 抽象处理者

java 复制代码
/**
 * 审批请求对象
 */
public class LeaveRequest {
    private String applicant;   // 申请人
    private int days;           // 请假天数
    private String reason;      // 请假原因
    
    // 构造方法、getter、setter 省略...
    public LeaveRequest(String applicant, int days, String reason) {
        this.applicant = applicant;
        this.days = days;
        this.reason = reason;
    }
    
    public String getApplicant() { return applicant; }
    public int getDays() { return days; }
    public String getReason() { return reason; }
}
java 复制代码
/**
 * 抽象审批处理者
 */
public abstract class ApprovalHandler {
    
    // 下一个处理者
    protected ApprovalHandler nextHandler;
    
    /**
     * 设置下一个处理者
     */
    public void setNext(ApprovalHandler nextHandler) {
        this.nextHandler = nextHandler;
    }
    
    /**
     * 处理请求的核心方法
     * 模板方法模式:子类实现具体的审批逻辑
     */
    public final void handle(LeaveRequest request) {
        if (canHandle(request)) {
            // 当前 Handler 能处理
            doHandle(request);
        } else if (nextHandler != null) {
            // 交给下一个处理者
            System.out.println("【" + getHandlerName() + "】无法处理,转交下一级...");
            nextHandler.handle(request);
        } else {
            // 没人能处理
            System.out.println("【系统】无人可以处理该请求!");
        }
    }
    
    /**
     * 判断是否能处理该请求
     */
    protected abstract boolean canHandle(LeaveRequest request);
    
    /**
     * 具体的处理逻辑
     */
    protected abstract void doHandle(LeaveRequest request);
    
    /**
     * 获取处理者名称(用于日志)
     */
    protected abstract String getHandlerName();
}

3.2 具体处理者实现

java 复制代码
/**
 * 组长审批:处理 1-3 天的请假
 */
public class GroupLeaderHandler extends ApprovalHandler {
    
    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() >= 1 && request.getDays() <= 3;
    }
    
    @Override
    protected void doHandle(LeaveRequest request) {
        System.out.println("【组长】审批通过:" + request.getApplicant() 
            + " 请假 " + request.getDays() + " 天,原因:" + request.getReason());
    }
    
    @Override
    protected String getHandlerName() {
        return "组长";
    }
}
java 复制代码
/**
 * 部门经理审批:处理 4-7 天的请假
 */
public class DeptManagerHandler extends ApprovalHandler {
    
    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() >= 4 && request.getDays() <= 7;
    }
    
    @Override
    protected void doHandle(LeaveRequest request) {
        System.out.println("【部门经理】审批通过:" + request.getApplicant() 
            + " 请假 " + request.getDays() + " 天,原因:" + request.getReason());
    }
    
    @Override
    protected String getHandlerName() {
        return "部门经理";
    }
}
java 复制代码
/**
 * HR 审批:处理 8 天以上的请假
 */
public class HRHandler extends ApprovalHandler {
    
    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() >= 8;
    }
    
    @Override
    protected void doHandle(LeaveRequest request) {
        System.out.println("【HR】审批通过:" + request.getApplicant() 
            + " 请假 " + request.getDays() + " 天,原因:" + request.getReason());
        System.out.println("【HR】提醒:超过 7 天的请假需要提交纸质材料!");
    }
    
    @Override
    protected String getHandlerName() {
        return "HR";
    }
}

3.3 客户端使用

java 复制代码
/**
 * 责任链客户端:组装责任链并发起请求
 */
public class LeaveApprovalClient {
    
    public static void main(String[] args) {
        // 1. 创建处理者
        ApprovalHandler groupLeader = new GroupLeaderHandler();
        ApprovalHandler deptManager = new DeptManagerHandler();
        ApprovalHandler hr = new HRHandler();
        
        // 2. 组装责任链:组长 → 经理 → HR
        groupLeader.setNext(deptManager);
        deptManager.setNext(hr);
        
        // 3. 创建请假请求
        LeaveRequest request1 = new LeaveRequest("张三", 2, "感冒请假");
        LeaveRequest request2 = new LeaveRequest("李四", 5, "家里有事");
        LeaveRequest request3 = new LeaveRequest("王五", 10, "出国旅游");
        
        // 4. 从链头开始处理
        System.out.println("===== 场景1:请假2天 =====");
        groupLeader.handle(request1);
        
        System.out.println("\n===== 场景2:请假5天 =====");
        groupLeader.handle(request2);
        
        System.out.println("\n===== 场景3:请假10天 =====");
        groupLeader.handle(request3);
    }
}

运行结果:

复制代码
===== 场景1:请假2天 =====
【组长】审批通过:张三 请假 2 天,原因:感冒请假

===== 场景2:请假5天 =====
【组长】无法处理,转交下一级...
【部门经理】审批通过:李四 请假 5 天,原因:家里有事

===== 场景3:请假10天 =====
【组长】无法处理,转交下一级...
【部门经理】无法处理,转交下一级...
【HR】审批通过:王五 请假 10 天,原因:出国旅游
【HR】提醒:超过 7 天的请假需要提交纸质材料!

3.4 动态构建责任链(从配置读取)

实际项目中,审批节点顺序往往从配置读取:

java 复制代码
/**
 * 基于配置动态构建责任链
 */
public class ChainBuilder {
    
    /**
     * 从配置中读取节点顺序,动态构建责任链
     * 配置示例:approval.chain=groupLeader,deptManager,hr
     */
    public static ApprovalHandler buildChain(String config) {
        String[] handlerNames = config.split(",");
        
        ApprovalHandler head = null;
        ApprovalHandler current = null;
        
        for (String name : handlerNames) {
            ApprovalHandler handler = createHandler(name.trim());
            if (head == null) {
                head = handler;
                current = handler;
            } else {
                current.setNext(handler);
                current = handler;
            }
        }
        
        return head;
    }
    
    private static ApprovalHandler createHandler(String name) {
        switch (name) {
            case "groupLeader": return new GroupLeaderHandler();
            case "deptManager": return new DeptManagerHandler();
            case "hr": return new HRHandler();
            default: throw new IllegalArgumentException("未知的 Handler:" + name);
        }
    }
    
    public static void main(String[] args) {
        // 从配置文件或数据库读取
        String chainConfig = "groupLeader,deptManager,hr";
        ApprovalHandler chain = buildChain(chainConfig);
        
        LeaveRequest request = new LeaveRequest("赵六", 6, "婚假");
        chain.handle(request);
    }
}

四、配合 Spring 实现优雅的责任链

4.1 使用 @Order 注解排序

Spring 的 @Order 注解可以帮我们自动排序 Handler,省去了手动 setNext 的麻烦。

java 复制代码
/**
 * 抽象 Handler,继承 Spring 的 Ordered 接口
 */
public abstract class SpringApprovalHandler implements Ordered {
    
    protected SpringApprovalHandler nextHandler;
    
    public void setNext(SpringApprovalHandler nextHandler) {
        this.nextHandler = nextHandler;
    }
    
    public final void handle(LeaveRequest request) {
        if (canHandle(request)) {
            doHandle(request);
        } else if (nextHandler != null) {
            nextHandler.handle(request);
        } else {
            throw new RuntimeException("无人可以处理该请求");
        }
    }
    
    protected abstract boolean canHandle(LeaveRequest request);
    protected abstract void doHandle(LeaveRequest request);
}
java 复制代码
@Component
@Order(1)  // 数字越小,优先级越高,排在越前面
public class SpringGroupLeaderHandler extends SpringApprovalHandler {
    
    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() <= 3;
    }
    
    @Override
    protected void doHandle(LeaveRequest request) {
        System.out.println("【Spring-组长】审批通过:" + request.getApplicant());
    }
    
    @Override
    public int getOrder() {
        return 1;
    }
}
java 复制代码
@Component
@Order(2)
public class SpringDeptManagerHandler extends SpringApprovalHandler {
    
    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() <= 7;
    }
    
    @Override
    protected void doHandle(LeaveRequest request) {
        System.out.println("【Spring-部门经理】审批通过:" + request.getApplicant());
    }
    
    @Override
    public int getOrder() {
        return 2;
    }
}
java 复制代码
@Component
@Order(3)
public class SpringHRHandler extends SpringApprovalHandler {
    
    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() > 7;
    }
    
    @Override
    protected void doHandle(LeaveRequest request) {
        System.out.println("【Spring-HR】审批通过:" + request.getApplicant());
    }
    
    @Override
    public int getOrder() {
        return 3;
    }
}

4.2 自动组装责任链

java 复制代码
/**
 * Spring 责任链配置类:自动收集所有 Handler 并组装成链
 */
@Configuration
public class ApprovalChainConfig {
    
    /**
     * 自动注入所有 SpringApprovalHandler,按 @Order 排序后组装成链
     */
    @Bean
    public SpringApprovalHandler approvalChain(List<SpringApprovalHandler> handlers) {
        // 按 @Order 排序(升序)
        handlers.sort(Comparator.comparingInt(SpringApprovalHandler::getOrder));
        
        // 组装责任链
        for (int i = 0; i < handlers.size() - 1; i++) {
            handlers.get(i).setNext(handlers.get(i + 1));
        }
        
        System.out.println("责任链组装完成,共 " + handlers.size() + " 个节点");
        return handlers.get(0);  // 返回链头
    }
}

4.3 Service 层使用

java 复制代码
@Service
public class LeaveService {
    
    @Autowired
    private SpringApprovalHandler approvalChain;
    
    /**
     * 提交请假申请
     */
    public void submitLeave(LeaveRequest request) {
        // 直接调用责任链,无需关心具体审批流程
        approvalChain.handle(request);
    }
}

4.4 更优雅的实现:利用 ApplicationContext

java 复制代码
/**
 * 更优雅的责任链管理器:运行时动态获取所有 Handler
 */
@Component
public class ApprovalChainManager implements ApplicationContextAware {
    
    private ApplicationContext applicationContext;
    private SpringApprovalHandler chainHead;
    
    @PostConstruct
    public void init() {
        // 从 Spring 容器中获取所有 Handler
        Map<String, SpringApprovalHandler> handlerMap = 
            applicationContext.getBeansOfType(SpringApprovalHandler.class);
        
        List<SpringApprovalHandler> handlers = new ArrayList<>(handlerMap.values());
        handlers.sort(Comparator.comparingInt(SpringApprovalHandler::getOrder));
        
        // 组装链
        for (int i = 0; i < handlers.size() - 1; i++) {
            handlers.get(i).setNext(handlers.get(i + 1));
        }
        
        this.chainHead = handlers.get(0);
    }
    
    public void process(LeaveRequest request) {
        chainHead.handle(request);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) 
            throws BeansException {
        this.applicationContext = applicationContext;
    }
}

五、Flowable 工作流引擎简介

5.1 Flowable 是什么?

Flowable 是一个用 Java 编写的轻量级业务流程引擎,支持 BPMN 2.0 标准。简单来说,它把流程定义从代码中抽离出来,用 XML 描述流程,运行时解析执行。

什么时候用 Flowable,什么时候用纯 Java 责任链?

维度 纯 Java 责任链 Flowable 工作流
流程复杂度 简单线性流程 复杂分支、会签、子流程
可视化需求 需要流程图、监控面板
动态调整 需重启或配置中心 可热部署流程定义
学习成本 较高
团队规模 小团队 中大型团队
审批历史 自己实现 原生支持

我的建议:

  • 简单的多级审批(3-5 个节点),用纯 Java 责任链足够
  • 复杂的业务流程(会签、条件分支、超时提醒),上 Flowable

5.2 Flowable 的 BPMN 流程图设计

请假流程的 BPMN XML 示例:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
             id="definitions"
             targetNamespace="http://flowable.org/bpmn20">

    <process id="leaveProcess" name="请假审批流程">
        
        <!-- 开始节点 -->
        <startEvent id="start" name="提交请假单"/>
        
        <!-- 组长审批 -->
        <userTask id="groupLeaderTask" name="组长审批"
                  flowable:assignee="${groupLeader}"/>
        
        <!-- 条件分支:请假天数 <= 3? -->
        <exclusiveGateway id="gateway1" name="判断天数"/>
        
        <!-- 部门经理审批 -->
        <userTask id="deptManagerTask" name="部门经理审批"
                  flowable:assignee="${deptManager}"/>
        
        <!-- 条件分支:请假天数 <= 7? -->
        <exclusiveGateway id="gateway2" name="判断天数"/>
        
        <!-- HR 审批 -->
        <userTask id="hrTask" name="HR 审批"
                  flowable:assignee="${hr}"/>
        
        <!-- 结束节点 -->
        <endEvent id="end" name="流程结束"/>
        
        <!-- 连线 -->
        <sequenceFlow id="flow1" sourceRef="start" targetRef="groupLeaderTask"/>
        <sequenceFlow id="flow2" sourceRef="groupLeaderTask" targetRef="gateway1"/>
        <sequenceFlow id="flow3" sourceRef="gateway1" targetRef="end">
            <conditionExpression xsi:type="tFormalExpression">
                ${days &lt;= 3}
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow4" sourceRef="gateway1" targetRef="deptManagerTask">
            <conditionExpression xsi:type="tFormalExpression">
                ${days &gt; 3}
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow5" sourceRef="deptManagerTask" targetRef="gateway2"/>
        <sequenceFlow id="flow6" sourceRef="gateway2" targetRef="end">
            <conditionExpression xsi:type="tFormalExpression">
                ${days &lt;= 7}
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow7" sourceRef="gateway2" targetRef="hrTask">
            <conditionExpression xsi:type="tFormalExpression">
                ${days &gt; 7}
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow8" sourceRef="hrTask" targetRef="end"/>
        
    </process>
</definitions>

5.3 Flowable 与 Spring Boot 整合

java 复制代码
/**
 * Flowable 配置类
 */
@Configuration
public class FlowableConfig {
    
    @Bean
    public SpringProcessEngineConfiguration processEngineConfiguration(
            DataSource dataSource, 
            PlatformTransactionManager transactionManager) {
        
        SpringProcessEngineConfiguration configuration = 
            new SpringProcessEngineConfiguration();
        
        configuration.setDataSource(dataSource);
        configuration.setTransactionManager(transactionManager);
        configuration.setDatabaseSchemaUpdate("true");
        configuration.setAsyncExecutorActivate(false);
        
        return configuration;
    }
    
    @Bean
    public ProcessEngine processEngine(SpringProcessEngineConfiguration configuration) {
        return configuration.buildProcessEngine();
    }
}
java 复制代码
@Service
public class FlowableLeaveService {
    
    @Autowired
    private RuntimeService runtimeService;
    
    @Autowired
    private TaskService taskService;
    
    /**
     * 启动请假流程
     */
    public String startLeaveProcess(LeaveRequest request) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("applicant", request.getApplicant());
        variables.put("days", request.getDays());
        variables.put("reason", request.getReason());
        variables.put("groupLeader", "组长张三");
        variables.put("deptManager", "经理李四");
        variables.put("hr", "HR王五");
        
        ProcessInstance instance = runtimeService
            .startProcessInstanceByKey("leaveProcess", variables);
        
        return instance.getId();
    }
    
    /**
     * 完成当前任务(审批通过)
     */
    public void completeTask(String taskId) {
        taskService.complete(taskId);
    }
}

六、责任链 vs 策略模式 vs 装饰器模式对比

这三种模式看起来有点像,容易搞混。我用一张表格帮你理清:

对比维度 责任链模式 策略模式 装饰器模式
核心意图 多个对象依次尝试处理请求 封装算法族,互相替换 动态给对象添加职责
结构特点 链式结构,A→B→C 平行结构,选其一 嵌套结构,层层包装
请求处理方式 一个处理即可,或全部传递 只选一个策略执行 每个装饰器都执行
Handler 关系 同级,不知道谁会处理 同级,客户端选择 嵌套,外层包装内层
适用场景 审批流、过滤器链、日志链 支付方式、排序算法 IO 流、权限包装
扩展性 新增节点,插入链中 新增策略,注册即可 新增装饰器,包装即可
典型框架 Servlet Filter、Dubbo Filter Spring 的 Resource 策略 Java IO 流

一句话区分:

  • 责任链:挨个问,谁能干谁来
  • 策略模式:挑一个,指定谁来
  • 装饰器:层层包,每人都干

七、踩坑指南

踩坑提醒 1:责任链过长导致性能问题

我见过一个项目,审批链有 20 多个节点,每个请求都要遍历一遍。如果 Handler 里还有数据库查询,性能直接爆炸。建议: 链长度控制在 10 个以内,复杂流程考虑用工作流引擎。
踩坑提醒 2:循环引用(A→B→C→A)

动态构建责任链时,如果配置写错了,可能出现循环引用,导致 StackOverflowError。建议: 组装链时做环检测,或者用有向无环图(DAG)校验。

java 复制代码
// 简单的环检测示例
public void setNextWithCheck(ApprovalHandler next) {
    Set<ApprovalHandler> visited = new HashSet<>();
    ApprovalHandler current = next;
    while (current != null) {
        if (visited.contains(current)) {
            throw new IllegalStateException("责任链存在循环引用!");
        }
        visited.add(current);
        current = current.nextHandler;
    }
    this.nextHandler = next;
}

踩坑提醒 3:所有 Handler 都不处理导致请求丢失

如果链尾没有兜底处理,请求可能"石沉大海"。建议: 链尾加一个 DefaultHandler,或者 handle 方法里抛出异常。
踩坑提醒 4:责任链的线程安全问题

如果 Handler 里有共享状态(比如计数器),多线程环境下会出问题。建议: Handler 尽量无状态,共享状态用 ThreadLocal 或原子类。

java 复制代码
// 错误示例:有状态 Handler
public class BadHandler extends ApprovalHandler {
    private int count = 0;  // 线程不安全!
    
    @Override
    protected void doHandle(LeaveRequest request) {
        count++;  // 多线程下计数混乱
    }
}

// 正确示例:无状态 Handler
public class GoodHandler extends ApprovalHandler {
    private static final AtomicInteger count = new AtomicInteger(0);
    
    @Override
    protected void doHandle(LeaveRequest request) {
        count.incrementAndGet();  // 线程安全
    }
}

八、问题与解答

Q1:责任链模式和拦截器链(Interceptor Chain)有什么区别?

A: 本质上是一个东西,只是叫法不同。

  • 责任链模式是设计模式层面的概念,强调"一个处理者处理完就结束"
  • 拦截器链是具体实现,比如 Spring 的 HandlerInterceptor,通常每个拦截器都会执行(preHandle→postHandle→afterCompletion),更像是"装饰器"的变体

Servlet Filter 链是两者的结合:每个 Filter 都可以选择"放行"或"拦截",既像责任链又像装饰器。

Q2:责任链中的请求对象一定要统一吗?

A: 不一定,但建议统一。

如果每个 Handler 处理的请求类型不同,可以用泛型:

java 复制代码
public abstract class Handler<T> {
    protected Handler<T> next;
    public abstract void handle(T request);
}

但项目中通常用一个统一的上下文对象(Context)封装所有信息,这样链的组装更灵活。

Q3:如果审批流程中有"会签"(多人同时审批)怎么办?

A: 纯 Java 责任链不太好处理会签场景,这是它的局限性。

解决方案:

  1. 会签节点内部用并行流或 CompletableFuture 处理
  2. 直接上 Flowable,它原生支持会签(multiInstance)
xml 复制代码
<!-- Flowable 会签示例 -->
<userTask id="counterSign" name="会签审批">
    <multiInstanceLoopCharacteristics 
        isSequential="false"
        flowable:collection="assigneeList"
        flowable:elementVariable="assignee">
        <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.5}</completionCondition>
    </multiInstanceLoopCharacteristics>
</userTask>

九、面试高频考点汇总

考点 1:什么是责任链模式?说说它的优缺点。

答案:

责任链模式是一种行为型设计模式,让多个对象都有机会处理请求,从而避免请求发送者和接收者的耦合。

优点:

  • 解耦发送者和接收者
  • 动态新增或删除处理节点
  • 符合单一职责原则

缺点:

  • 链过长影响性能
  • 调试困难,不知道请求被谁处理了
  • 如果配置不当可能出现循环引用

考点 2:你在项目中是怎么用责任链模式的?

答案:

我在 XX 项目中用责任链模式实现了请假审批流。核心思路是:

  1. 定义抽象 Handler,包含 setNext 和 handle 方法
  2. 每个审批级别(组长、经理、HR)实现具体的 Handler
  3. 用 Spring 的 @Order 注解排序,自动组装责任链
  4. 审批规则从配置中心读取,支持动态调整

代码结构清晰,新增审批节点只需要新增一个类,不用改现有代码。

考点 3:责任链模式和策略模式有什么区别?

答案:

维度 责任链 策略
执行方式 依次尝试,一个处理即可 客户端选择,只执行一个
结构 链式 平行
客户端角色 只发请求,不关心谁处理 主动选择策略

举个例子:审批流是责任链(挨个问谁能批),支付选方式是策略(主动选支付宝还是微信)。

考点 4:如何避免责任链中的循环引用?

答案:

三种方式:

  1. 编码期检查: 组装链时遍历检测是否有环
  2. 配置校验: 用 DAG 算法校验配置文件的依赖关系
  3. 运行时防护: 设置最大遍历深度,超过则抛异常
java 复制代码
public void handle(Request req) {
    handleWithDepth(req, 0);
}

private void handleWithDepth(Request req, int depth) {
    if (depth > MAX_CHAIN_LENGTH) {
        throw new IllegalStateException("责任链可能存在循环!");
    }
    // ... 处理逻辑
}

考点 5:Spring 中哪些地方用到了责任链模式?

答案:

  • Servlet Filter: 多个 Filter 组成链,依次处理请求
  • Spring MVC Interceptor: preHandle 返回 true 则继续传递
  • Dubbo Filter: 服务调用前后的过滤器链
  • MyBatis Plugin: 拦截器链处理 SQL

十、模拟面试官提问和参考答案

场景题 1:设计一个电商订单的售后审批流

题目: 电商平台的售后申请需要多级审批:客服初审(判断是否符合售后条件)→ 仓库审核(判断商品是否已退回)→ 财务审核(判断金额是否合理)→ 主管终审。请用责任链模式设计,要求支持"跳过"某些节点。

参考答案:

java 复制代码
/**
 * 售后请求上下文
 */
public class AfterSaleRequest {
    private String orderId;
    private BigDecimal amount;
    private boolean itemReturned;   // 商品是否已退回
    private boolean skipWarehouse;  // 是否跳过仓库审核
    // ... getter/setter
}

/**
 * 支持跳过的抽象 Handler
 */
public abstract class AfterSaleHandler {
    protected AfterSaleHandler next;
    
    public void setNext(AfterSaleHandler next) {
        this.next = next;
    }
    
    public final void handle(AfterSaleRequest request) {
        // 判断是否需要跳过当前节点
        if (shouldSkip(request)) {
            System.out.println("【" + getName() + "】被跳过");
            if (next != null) next.handle(request);
            return;
        }
        
        if (canHandle(request)) {
            doHandle(request);
        } else if (next != null) {
            next.handle(request);
        }
    }
    
    protected abstract boolean shouldSkip(AfterSaleRequest request);
    protected abstract boolean canHandle(AfterSaleRequest request);
    protected abstract void doHandle(AfterSaleRequest request);
    protected abstract String getName();
}

// 客服初审:小额订单自动跳过仓库和财务
public class CustomerServiceHandler extends AfterSaleHandler {
    @Override
    protected boolean shouldSkip(AfterSaleRequest request) {
        return false;  // 客服永不跳过
    }
    
    @Override
    protected boolean canHandle(AfterSaleRequest request) {
        return true;  // 客服只负责初审,永远通过
    }
    
    @Override
    protected void doHandle(AfterSaleRequest request) {
        System.out.println("【客服】初审通过");
        // 小额自动设置跳过标记
        if (request.getAmount().compareTo(new BigDecimal("100")) < 0) {
            request.setSkipWarehouse(true);
        }
    }
    
    @Override protected String getName() { return "客服"; }
}

// 仓库审核
public class WarehouseHandler extends AfterSaleHandler {
    @Override
    protected boolean shouldSkip(AfterSaleRequest request) {
        return request.isSkipWarehouse();
    }
    
    @Override
    protected boolean canHandle(AfterSaleRequest request) {
        return request.isItemReturned();
    }
    
    @Override
    protected void doHandle(AfterSaleRequest request) {
        System.out.println("【仓库】确认商品已退回");
    }
    
    @Override protected String getName() { return "仓库"; }
}

场景题 2:责任链如何实现"审批通过"和"审批驳回"两种结果?

题目: 上面的审批流,每个节点都可以选择"通过"或"驳回",驳回后流程直接结束。怎么设计?

参考答案:

java 复制代码
public enum ApprovalResult {
    APPROVED,   // 通过,继续下一级
    REJECTED,   // 驳回,流程结束
    TRANSFERRED // 转交,继续下一级
}

public abstract class ApprovalHandlerV2 {
    protected ApprovalHandlerV2 next;
    
    public final void handle(LeaveRequest request, ApprovalContext context) {
        ApprovalResult result = process(request);
        context.recordLog(getName(), result);
        
        if (result == ApprovalResult.REJECTED) {
            context.setFinalResult("已驳回 by " + getName());
            return;  // 驳回,流程结束
        }
        
        if (next != null) {
            next.handle(request, context);
        } else {
            context.setFinalResult("已通过全部审批");
        }
    }
    
    protected abstract ApprovalResult process(LeaveRequest request);
    protected abstract String getName();
}

场景题 3:如何实现责任链的"中断恢复"?

题目: 审批流执行到一半,服务重启了,怎么恢复?

参考答案:

需要引入持久化机制:

  1. 数据库记录审批状态: 每个节点审批后写入审批记录表
  2. 重启后恢复: 根据订单 ID 查询当前审批到哪个节点
  3. 从断点继续: 找到对应的 Handler,继续执行
java 复制代码
@Service
public class ResumableApprovalService {
    
    @Autowired
    private ApprovalRecordDao recordDao;
    
    public void resume(String orderId) {
        // 查询最后一条审批记录
        ApprovalRecord lastRecord = recordDao.findLastByOrderId(orderId);
        
        // 找到下一个待审批的 Handler
        ApprovalHandler nextHandler = findHandlerByName(lastRecord.getNextHandlerName());
        
        // 重新构建请求,继续审批
        LeaveRequest request = rebuildRequest(orderId);
        nextHandler.handle(request);
    }
}

场景题 4:责任链模式如何与 AOP 结合做日志和权限校验?

题目: 想在每个审批节点前后自动记录日志和校验权限,不侵入业务代码。

参考答案:

用 Spring AOP 代理 Handler:

java 复制代码
@Aspect
@Component
public class ApprovalAspect {
    
    @Around("execution(* com.example.handler.*.doHandle(..))")
    public Object aroundHandle(ProceedingJoinPoint point) throws Throwable {
        String handlerName = point.getTarget().getClass().getSimpleName();
        
        // 前置:记录日志 + 权限校验
        System.out.println("[AOP] " + handlerName + " 开始处理");
        checkPermission(handlerName);
        
        long start = System.currentTimeMillis();
        Object result = point.proceed();
        long cost = System.currentTimeMillis() - start;
        
        // 后置:记录耗时
        System.out.println("[AOP] " + handlerName + " 处理完成,耗时 " + cost + "ms");
        
        return result;
    }
    
    private void checkPermission(String handlerName) {
        // 权限校验逻辑
    }
}

场景题 5:如何设计一个支持"条件分支"的责任链?

题目: 审批流程不是线性的,比如:金额 < 1000 走简易流程,金额 >= 1000 走完整流程。

参考答案:

引入"路由 Handler",根据条件选择不同的子链:

java 复制代码
/**
 * 路由 Handler:根据条件选择分支
 */
public class RouterHandler extends ApprovalHandler {
    
    private ApprovalHandler simpleChain;   // 简易流程链头
    private ApprovalHandler fullChain;     // 完整流程链头
    
    @Override
    protected boolean canHandle(LeaveRequest request) {
        return true;  // 路由 Handler 永远能处理
    }
    
    @Override
    protected void doHandle(LeaveRequest request) {
        if (request.getAmount() < 1000) {
            System.out.println("【路由】金额 < 1000,走简易流程");
            simpleChain.handle(request);
        } else {
            System.out.println("【路由】金额 >= 1000,走完整流程");
            fullChain.handle(request);
        }
    }
    
    @Override protected String getHandlerName() { return "路由"; }
}

或者更通用的方式:用 Map 存储条件→Handler 的映射:

java 复制代码
public class ConditionalRouter {
    private List<ConditionMapping> mappings = new ArrayList<>();
    
    public void addMapping(Predicate<Request> condition, ApprovalHandler handler) {
        mappings.add(new ConditionMapping(condition, handler));
    }
    
    public void route(Request request) {
        for (ConditionMapping mapping : mappings) {
            if (mapping.condition.test(request)) {
                mapping.handler.handle(request);
                return;
            }
        }
        throw new RuntimeException("没有匹配的路由规则");
    }
    
    private static class ConditionMapping {
        Predicate<Request> condition;
        ApprovalHandler handler;
        // ...
    }
}

十一、互动话题

你在项目中写过最复杂的审批流是什么样的?有没有遇到过"审批节点改来改去,代码改到崩溃"的经历?欢迎在评论区聊聊你的踩坑故事,点赞最高的送《设计模式之禅》电子版!


十二、参考资料

  1. Flowable 官方文档 - BPMN 2.0 介绍
  2. Refactoring Guru - 责任链模式详解