什么是责任链模式?应用场景有哪些?
面试考察点
-
设计思想理解:面试官不仅仅是想知道你能背出责任链的定义,更是想知道你是否理解 "把多个处理者串成一条链,请求沿着链传递,直到有人处理" 这个核心思想,以及它解决了什么问题。
-
实战编码能力:能否手写一个责任链,能否讲清楚链的构建方式和传递逻辑。有些面试官会让你现场写一个简单的责任链。
-
框架源码关联:能否把责任链和 Servlet Filter、Spring Interceptor、MyBatis Plugin 等框架实现联系起来,这体现了你的源码阅读深度。
核心答案
一句话定义:责任链模式将多个处理器串成一条链,请求沿着链依次传递,每个处理器决定是自己处理、传递给下一个处理器、还是两者都做。发送请求的一方不需要知道谁会最终处理,实现了请求发送者和接收者的解耦。
生活中的例子:公司请假审批流程——3 天以内组长批,3~7 天经理批,7 天以上总监批。你提交请假申请后,请求会沿着 "组长 → 经理 → 总监" 这条链自动流转,你不需要关心最终是谁批的。
上面的图展示了责任链的基本流转。核心思路就是:
- 链式结构:每个处理器持有下一个处理器的引用,形成一条链
- 自动传递:请求从链头开始,沿链依次传递
- 自主决策:每个处理器自行决定是处理、传递还是终止
深度解析
一、手写责任链
先用一个完整的代码示例把责任链的核心结构搞明白。
// 抽象处理器
public abstract class Handler {
protected Handler next; // 持有下一个处理器的引用
public Handler setNext(Handler next) {
this.next = next;
return next; // 返回下一个处理器,方便链式调用
}
public abstract void handle(String request);
}
// 具体处理器 A:组长
public class GroupLeaderHandler extends Handler {
@Override
public void handle(String request) {
System.out.println("组长收到请求:" + request);
if (request.contains("3 天以内")) {
System.out.println("→ 组长审批通过 ✓");
} else {
System.out.println("→ 组长权限不够,转交给上级...");
if (next != null) {
next.handle(request);
}
}
}
}
// 具体处理器 B:经理
public class ManagerHandler extends Handler {
@Override
public void handle(String request) {
System.out.println("经理收到请求:" + request);
if (request.contains("7 天以内")) {
System.out.println("→ 经理审批通过 ✓");
} else {
System.out.println("→ 经理权限不够,转交给上级...");
if (next != null) {
next.handle(request);
}
}
}
}
// 具体处理器 C:总监
public class DirectorHandler extends Handler {
@Override
public void handle(String request) {
System.out.println("总监收到请求:" + request);
System.out.println("→ 总监审批通过 ✓");
}
}
组装链条并使用:
// 构建责任链
Handler groupLeader = new GroupLeaderHandler();
Handler manager = new ManagerHandler();
Handler director = new DirectorHandler();
groupLeader.setNext(manager).setNext(director);
// 测试
groupLeader.handle("请假 2 天,回老家");
System.out.println("-----------");
groupLeader.handle("请假 5 天,去旅游");
System.out.println("-----------");
groupLeader.handle("请假 10 天,休年假");
输出:
组长收到请求:请假 2 天,回老家
→ 组长审批通过 ✓
-----------
组长收到请求:请假 5 天,去旅游
→ 组长权限不够,转交给上级...
经理收到请求:请假 5 天,去旅游
→ 经理审批通过 ✓
-----------
组长收到请求:请假 10 天,休年假
→ 组长权限不够,转交给上级...
经理收到请求:请假 10 天,休年假
→ 经理权限不够,转交给上级...
总监收到请求:请假 10 天,休年假
→ 总监审批通过 ✓
代码结构非常清晰:每个处理器只关心自己能不能处理,能就处理,不能就传给下一个。新增处理器只需要创建一个类,然后插入链中即可,完全不影响已有的处理器代码。这就是责任链模式的核心优势——开闭原则。
二、Servlet Filter 中的责任链
说完了基础写法,来看看真实框架怎么用的。Servlet 的 Filter 就是最经典的责任链实现。
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("请求到达,前置处理...");
chain.doFilter(request, response); // 传给下一个 Filter
System.out.println("响应返回,后置处理...");
}
}
它的流转方式和纯责任链略有不同——FilterChain 不只是单向传递,而是像一个管道:
┌───────────────────────────────────────────────────────┐
│ Servlet Filter 责任链(管道模型) │
├───────────────────────────────────────────────────────┤
│ │
│ Request ─► │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Filter A │─►│ Filter B │─►│ Filter C │──► Servlet │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ◄─ ◄─ ◄─ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Filter A │◄─│ Filter B │◄─│ Filter C │◄── Response │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 每个 Filter 的 chain.doFilter() 把请求传给下一个, │
│ 执行完后续逻辑后,响应按相反顺序依次返回 │
│ │
└───────────────────────────────────────────────────────┘
Servlet Filter 采用了双向传递的管道模型:
- 请求阶段:
Filter A → Filter B → Filter C → Servlet,每个 Filter 在chain.doFilter()之前的逻辑按顺序执行 - 响应阶段:
Filter C → Filter B → Filter A,每个 Filter 在chain.doFilter()之后的逻辑按逆序执行
这种设计特别适合统一处理编码转换、登录校验、日志记录等横切关注点。
三、Spring Interceptor 中的责任链
Spring MVC 的 HandlerInterceptor 也是责任链,但接口设计更清晰:
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) {
// 前置处理:返回 true 继续传递,返回 false 终止链
String token = request.getHeader("Authorization");
if (token == null) {
response.setStatus(401);
return false; // 终止传递
}
return true; // 继续传给下一个 Interceptor
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
// 后置处理
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// 完成后处理(无论是否异常都会执行)
}
}
preHandle() 返回 false 就能直接截断整条链,这个设计比 Servlet Filter 更直观。
四、责任链 vs 装饰器 vs 拦截器
面试时可能会被问到这几者的区别,简单对比一下:
| 对比项 | 责任链模式 | 装饰器模式 | 拦截器 |
|---|---|---|---|
| 核心目的 | 分离请求发送者和处理者 | 动态增强对象功能 | 在流程前后插入逻辑 |
| 链路特点 | 可中断、可跳过 | 不中断,逐层增强 | 可中断 |
| 典型场景 | 审批流程、过滤器链 | IO 流包装、BufferedInputStream | Spring Interceptor |
| 关注点 | "谁该处理这个请求" | "如何增强这个对象" | "流程执行前后做什么" |
其实责任链和装饰器在代码结构上很像(都是链式调用),区别在于意图——责任链关注的是 "谁能处理",装饰器关注的是 "怎么增强"。
五、实际应用场景
1. 权限校验链
多个权限规则串成一条链,请求进来后依次经过:登录校验 → 角色校验 → 权限校验,任何一环不通过直接拒绝。
2. 日志 / 审计链
请求进来后依次经过:操作日志记录 → 敏感数据脱敏 → 审计信息记录。
3. 参数校验链
表单提交的数据依次经过:非空校验 → 格式校验 → 业务规则校验 → 数据库唯一性校验。
4. 框架中的责任链
| 框架 | 责任链实现 | 作用 |
|---|---|---|
| Servlet | Filter + FilterChain | 编码、登录、日志等 |
| Spring MVC | HandlerInterceptor | 权限、日志、性能监控 |
| MyBatis | Interceptor(Plugin) | 分页、SQL 打印、慢查询监控 |
| Dubbo | Filter 链 | 监控、限流、负载均衡 |
| Netty | ChannelPipeline + ChannelHandler | 编解码、业务处理 |
面试高频追问
-
责任链模式和装饰器模式有什么区别?
- 责任链关注 "谁来处理",可以中断或跳过;装饰器关注 "怎么增强",不会中断链路。代码结构相似,设计意图不同。
-
如何动态增删责任链中的处理器?
- 用一个
List<Handler>维护处理器列表,运行时动态增删。很多框架(如 Spring 的InterceptorRegistry)就是这么做的。
- 用一个
-
责任链会不会导致性能问题?
- 链太长确实会影响性能,每个请求都要经过所有处理器。生产环境要注意控制链的长度,避免不必要的处理器。也可以用短路机制(处理完即停)来优化。
-
Servlet Filter 和 Spring Interceptor 的区别?
- Filter 是 Servlet 规范层面的,作用范围是所有请求(包括静态资源);Interceptor 是 Spring MVC 层面的,只拦截 Controller 方法。执行顺序是 Filter → Interceptor → Controller。
常见面试变体
- "手写一个责任链模式"
- "Servlet Filter 的底层原理是什么?"
- "Spring 拦截器和过滤器的区别?"
- "你项目中哪里用到了责任链模式?"
记忆口诀
核心思想:一链串多手,各管各的活,能干自己干,干不了往下传。
应用场景:审批流程、过滤器链、参数校验、权限校验。
与装饰器的区别:责任链问 "谁来处理",装饰器问 "怎么增强"。
总结
责任链模式的精髓就是把多个处理器串成链,请求沿链传递,每个处理器自主决定处理或转发。面试时先说概念,再手写一个简单实现,然后结合 Servlet Filter 和 Spring Interceptor 讲框架中的应用,最后聊一下和装饰器模式的区别。这条线下来,面试官对你的设计模式功底基本就心里有数了。
