Java 23 种设计模式:从踩坑到精通 | 责任链模式 —— 请求流转,审批流程的本质
摘要:当请求需要经过多个处理者,且处理链在运行时才能确定时,写死
if-else链会让代码严重耦合、难以扩展。责任链模式通过将处理者串联成一条链,让请求沿着链自动传递,直到被某个处理者处理。本文从 OA 审批流程出发,完整讲解纯责任链与不纯责任链的实现,结合 Servlet Filter、Spring Interceptor 等框架应用,并给出 Spring 自动装配链的实战写法,帮你彻底掌握“请求传递,各司其职”的设计精髓。
🗺️ 本文阅读地图(3 分钟速览)
- 为什么
if-else审批链一定会炸?- 纯责任链 vs 不纯责任链(99% 的人搞混)
- 手写 OA 审批链(可直接抄)
- Spring 自动装配责任链(生产级写法)
- Servlet Filter / Spring Interceptor 源码级映射
- 面试必问 + 面试官追问连环炮
📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:代理模式 | 当前:责任链模式 | 下一篇:命令模式
🔗 返回系列总目录
文章目录
1. 从“审批流程”的层层传递说起
假设你在开发 OA 系统的请假审批功能:员工提交请假申请后,需要依次经过直属领导、部门经理、HR 三个角色审批,每个角色可能批准、驳回或转交下一级。如果硬编码:
if (leader.approve(request)) {
if (manager.approve(request)) {
if (hr.approve(request)) {
// 通过
}
}
}
这种写法将审批顺序死死绑定在代码中,当审批流程变化(如增加财务审批环节)时,必须修改核心代码,极易引入 Bug。更糟糕的是,如果需要在运行时动态调整审批顺序(比如根据请假天数跳过某些审批节点),这种嵌套结构完全无法应对。
责任链模式(Chain of Responsibility Pattern)通过将处理者组织成一条链,让请求沿着链自动传递,直到某个处理者处理它。处理者和请求发送者都只关心自己的职责,链的结构可以灵活调整。
1.1 你的场景该不该用责任链?
| 判断标准 | 是 → 用责任链 | 否 → 用其他方式 |
|---|---|---|
| 有多个对象可以处理同一请求,且处理器动态确定 | ✅ | ❌ |
| 需要向多个处理器提交请求,但不明确最终由谁处理 | ✅ | ❌ |
| 处理器顺序需要灵活调整,甚至运行时动态改变 | ✅ | ❌ |
| 处理逻辑非常简单,且处理器固定不变 | ❌ | 直接调用即可 |
❌ 滥用责任链的典型症状:链长度超过 5 个节点、每个节点只做简单日志、Debug 像走迷宫。此时应优先考虑策略模式(规则固定)或编排器(强顺序强事务)。
2. 模式定义与 UML 结构
责任链模式 使多个对象都有机会处理请求,从而避免请求的发送者与接收者之间的耦合。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。它属于 行为型设计模式。

从上方的 UML 类图中可以看出,责任链模式主要由以下角色构成:
- 抽象处理者(
Approver):它是链式调用的核心模板。Approver不仅定义了handleRequest处理规范,最关键的是内部持有一个nextApprover引用指向自己,从而构成了“自己调用自己”的链式结构。 - 具体处理者(
Leader/Manager/HR):它们各自独立封装了审批规则,职责清晰:- Leader(直属领导):只看 ≤3 天的假条,超过就甩给下一节点。
- Manager(部门经理):只看 ≤7 天的假条,超过继续往下传。
- HR:作为链条的兜底节点,审批 ≤30 天的假条并做最终把关。
核心机制:请求从 Leader 进入链中,遵循“各司其职”的原则传递。这种结构的好处是,如果某天需要增加一个“财务审批”节点,你只需新增一个 Finance 类,并把它像乐高一样拼进链中,完全不用修改 Leader 或 Manager 的内部代码。
3. 纯责任链 vs 不纯责任链
| 对比维度 | ✅ 纯责任链:非黑即白 | 🔄 不纯责任链:人人有份 |
|---|---|---|
| 处理者行为 | 要么处理请求,要么转发给下一个处理者,二者互斥 | 每个处理者都可以加工请求,然后继续传递 |
| 请求终止 | 一定被某个处理者处理,或到达链尾仍未被处理 | 可能被多个处理者依次处理 |
| 典型应用 | 审批流程(一个节点通过才往下) | Servlet Filter 链、Spring Interceptor |
Servlet Filter 对比表(纯 vs 不纯):
| 维度 | 审批链(纯责任链) | Servlet Filter(不纯责任链) |
|---|---|---|
| 是否中断传递 | ✅ 是(通过则停止) | ❌ 否(通常每个 Filter 都执行) |
| 是否处理响应 | ❌ 只处理请求 | ✅ 请求和响应都走一遍链 |
| 典型用途 | 业务规则审批 | 横切逻辑(日志、编码、鉴权) |
实际开发中,不纯责任链更常见,如 Filter 链、拦截器链。
4. 代码实现:OA 审批责任链(纯责任链)
4.1 请求类
public class LeaveRequest {
private String name;
private int days;
private String reason;
public LeaveRequest(String name, int days, String reason) {
this.name = name;
this.days = days;
this.reason = reason;
}
public String getName() { return name; }
public int getDays() { return days; }
public String getReason() { return reason; }
}
💬 白话:把“请假天数”封装进请求对象,而不是散落在
if里。
📌 设计点:请求对象携带所有上下文,处理者只依赖请求本身,不依赖外部环境。
4.2 抽象处理者
public abstract class Approver {
protected Approver nextApprover;
public void setNext(Approver next) {
this.nextApprover = next;
}
public abstract void handleRequest(LeaveRequest request);
}
4.3 具体处理者
public class Leader extends Approver {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 3) {
System.out.println("直属领导批准 " + request.getName() + " 请假 " + request.getDays() + " 天");
} else if (nextApprover != null) {
System.out.println("直属领导无权审批,转交下一级...");
nextApprover.handleRequest(request);
}
}
}
public class Manager extends Approver {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 7) {
System.out.println("部门经理批准 " + request.getName() + " 请假 " + request.getDays() + " 天");
} else if (nextApprover != null) {
System.out.println("部门经理无权审批,转交下一级...");
nextApprover.handleRequest(request);
}
}
}
public class HR extends Approver {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 30) {
System.out.println("HR 批准 " + request.getName() + " 请假 " + request.getDays() + " 天");
} else {
System.out.println("请假天数过长,不予批准");
}
}
}
💬 白话:每个角色只看自己能批多少天,超过就甩给下一个,最后 HR 兜底。
4.4 客户端(手动组装链)
Approver leader = new Leader();
Approver manager = new Manager();
Approver hr = new HR();
leader.setNext(manager);
manager.setNext(hr);
leader.handleRequest(new LeaveRequest("张三", 2, "事假"));
// 直属领导批准
leader.handleRequest(new LeaveRequest("李四", 5, "病假"));
// 直属领导 → 部门经理批准
leader.handleRequest(new LeaveRequest("王五", 10, "年假"));
// 直属领导 → 部门经理 → HR 批准
5. 代码实现:请求处理链(不纯责任链,类似 Filter)
5.1 抽象处理者
public abstract class FilterChainHandler {
protected FilterChainHandler next;
public void setNext(FilterChainHandler next) {
this.next = next;
}
public void doFilter(String request) {
handle(request);
if (next != null) {
next.doFilter(request);
}
}
protected abstract void handle(String request);
}
💬 白话:每个 Filter 执行完自己的逻辑后,自动把请求传给下一个。
5.2 具体处理者
public class LogFilter extends FilterChainHandler {
@Override
protected void handle(String request) {
System.out.println("[日志] 记录请求:" + request);
}
}
public class AuthFilter extends FilterChainHandler {
@Override
protected void handle(String request) {
System.out.println("[权限] 校验请求权限...");
}
}
5.3 客户端
FilterChainHandler log = new LogFilter();
FilterChainHandler auth = new AuthFilter();
log.setNext(auth);
log.doFilter("/api/user");
// [日志] 记录请求:/api/user
// [权限] 校验请求权限...
6. 生产级写法:Spring 自动装配责任链
手动 setNext 太“教学化”。在实际项目中,我们可以利用 Spring 的依赖注入自动组装链。
@Component
public class ApprovalChain {
private final List<Approver> approvers;
// Spring 自动注入所有 Approver 实现类,按 @Order 排序
public ApprovalChain(List<Approver> approvers) {
this.approvers = approvers;
link();
}
private void link() {
for (int i = 0; i < approvers.size() - 1; i++) {
approvers.get(i).setNext(approvers.get(i + 1));
}
}
public void handleRequest(LeaveRequest request) {
if (!approvers.isEmpty()) {
approvers.get(0).handleRequest(request);
}
}
}
💡 亮点:新增审批节点只需加一个
@Component并实现Approver接口,链自动组装,完全符合开闭原则。
7. 优缺点一览
| 优点 | 缺点 |
|---|---|
| 解耦:请求发送者与接收者解耦,各自独立变化 | 性能:链太长时请求遍历会影响性能 |
| 灵活:可动态增加或删除处理者,修改链的顺序 | 调试不便:链式调用递归或循环,排查问题需要顺藤摸瓜 |
| 符合单一职责:每个处理者只处理自己的职责 | 可能不被处理:纯责任链下若未正确配置,请求可能“丢失” |
| 可扩展:新增处理者只需加新类,无需修改现有逻辑 |
⚠️ 链 > 5 就要警惕:考虑拆分为多个子链,或引入规则引擎。
8. 框架与实践中的应用
8.1 Servlet Filter 链
javax.servlet.Filter 是不纯责任链的经典实现。每个 Filter 可以选择放行或拦截,请求和响应都会经过整条链。
public class EncodingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
// 前置处理
chain.doFilter(req, res); // 交给下一个 Filter
// 后置处理
}
}
8.2 Spring Interceptor 链
Spring MVC 的 HandlerInterceptor 通过 preHandle()、postHandle()、afterCompletion() 控制请求的预处理和后处理,也是责任链的变体。
8.3 Dubbo Filter 链
Dubbo 的服务调用过滤链通过 SPI 机制链式加载 MonitorFilter、ExceptionFilter 等,是责任链在 RPC 框架中的典型应用。
9. 面试必问 + 面试官追问连环炮
基础必问
- 责任链模式的优点? → 解耦请求与处理,灵活扩展,符合单一职责。
- Servlet Filter 是什么模式? → 不纯责任链模式,FilterChain 就是链。
- 纯责任链与不纯责任链的区别? → 纯责任链处理者要么处理要么转发;不纯责任链每个处理者都可以部分处理并继续传递。
🎤 面试官追问
- “责任链会不会有循环引用?”
👉 会,需在setNext时检测next == this,或使用List结构避免环形依赖。 - “如何保证责任链一定被处理?”
👉 设置兜底处理者(Tail Handler),例如 HR 节点作为最终审批人。 - “Spring 的 Interceptor 是责任链吗?”
👉 是,但preHandle可以短路终止链的传递。 - “如何避免链太长影响性能?”
👉 设置超时、熔断,或限制链长度 ≤ 5。 - “责任链和策略模式如何选择?”
👉 责任链关注“谁来处理”,策略关注“怎么处理”。二者可组合使用:链负责路由,策略负责执行。
🎉 恭喜:如果你能立刻说出 Servlet Filter 是责任链模式,Spring Interceptor 也基于责任链思想,并能区分纯与不纯责任链的差异,你已经掌握了行为型模式中最常见的“请求传递”设计。
10. 六大设计原则在责任链模式中的体现
| 设计原则 | 在责任链模式中的体现 |
|---|---|
| 单一职责原则(SRP) | 每个处理者只负责一种审批规则或处理逻辑 |
| 开闭原则(OCP) | 新增处理者无需修改原有代码,只需调整链的装配 |
| 里氏替换原则(LSP) | 所有具体处理者都可替换抽象 Handler |
| 依赖倒置原则(DIP) | 客户端依赖抽象 Handler,不依赖具体处理者 |
| 接口隔离原则(ISP) | 抽象处理者接口方法精简,只定义处理与设置下一个 |
| 迪米特法则(LoD) | 请求只与第一个处理者交互,无需知道链的结构 |
《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 上一篇:Java 23 种设计模式:从踩坑到精通 | 代理模式 —— 你的 AOP 就是用代理实现的
- 当前:Java 23 种设计模式:从踩坑到精通 | 责任链模式 —— 请求流转,审批流程的本质(你在这里)
- 下一篇:Java 23 种设计模式:从踩坑到精通 | 命令模式 —— 把操作封装成对象,实现撤销与排队 🚧 即将发布
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理……
- 行为型模式汇总:观察者、策略、模板方法……
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇:命令模式:为什么 Ctrl+Z 能撤销?——把操作封装成对象,实现事务回滚与任务队列!🚧 即将发布,敬请关注!
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。
1292

被折叠的 条评论
为什么被折叠?



