写在前面:说实话,我见过太多人写审批流,上来就是一堆 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 装饰器模式对比)
- 七、踩坑指南
- 八、问题与解答
-
- [Q1:责任链模式和拦截器链(Interceptor Chain)有什么区别?](#Q1:责任链模式和拦截器链(Interceptor Chain)有什么区别?)
- Q2:责任链中的请求对象一定要统一吗?
- Q3:如果审批流程中有"会签"(多人同时审批)怎么办?
- 九、面试高频考点汇总
-
- [考点 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│
└────────┘ └────────┘ └────────┘
三个核心角色:
- Handler(抽象处理者):定义处理请求的接口,包含设置下一个处理者的方法
- ConcreteHandler(具体处理者):实现具体的处理逻辑,处理不了就交给下一个
- 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 <= 3}
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="gateway1" targetRef="deptManagerTask">
<conditionExpression xsi:type="tFormalExpression">
${days > 3}
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" sourceRef="deptManagerTask" targetRef="gateway2"/>
<sequenceFlow id="flow6" sourceRef="gateway2" targetRef="end">
<conditionExpression xsi:type="tFormalExpression">
${days <= 7}
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" sourceRef="gateway2" targetRef="hrTask">
<conditionExpression xsi:type="tFormalExpression">
${days > 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 责任链不太好处理会签场景,这是它的局限性。
解决方案:
- 会签节点内部用并行流或 CompletableFuture 处理
- 直接上 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 项目中用责任链模式实现了请假审批流。核心思路是:
- 定义抽象 Handler,包含 setNext 和 handle 方法
- 每个审批级别(组长、经理、HR)实现具体的 Handler
- 用 Spring 的 @Order 注解排序,自动组装责任链
- 审批规则从配置中心读取,支持动态调整
代码结构清晰,新增审批节点只需要新增一个类,不用改现有代码。
考点 3:责任链模式和策略模式有什么区别?
答案:
| 维度 | 责任链 | 策略 |
|---|---|---|
| 执行方式 | 依次尝试,一个处理即可 | 客户端选择,只执行一个 |
| 结构 | 链式 | 平行 |
| 客户端角色 | 只发请求,不关心谁处理 | 主动选择策略 |
举个例子:审批流是责任链(挨个问谁能批),支付选方式是策略(主动选支付宝还是微信)。
考点 4:如何避免责任链中的循环引用?
答案:
三种方式:
- 编码期检查: 组装链时遍历检测是否有环
- 配置校验: 用 DAG 算法校验配置文件的依赖关系
- 运行时防护: 设置最大遍历深度,超过则抛异常
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:如何实现责任链的"中断恢复"?
题目: 审批流执行到一半,服务重启了,怎么恢复?
参考答案:
需要引入持久化机制:
- 数据库记录审批状态: 每个节点审批后写入审批记录表
- 重启后恢复: 根据订单 ID 查询当前审批到哪个节点
- 从断点继续: 找到对应的 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;
// ...
}
}
十一、互动话题
你在项目中写过最复杂的审批流是什么样的?有没有遇到过"审批节点改来改去,代码改到崩溃"的经历?欢迎在评论区聊聊你的踩坑故事,点赞最高的送《设计模式之禅》电子版!