文章目录
- 一、概述
-
- [1.1 结构与角色](#1.1 结构与角色)
- [1.2 适用场景](#1.2 适用场景)
- 二、实现方式
-
- [2.1 基础实现](#2.1 基础实现)
- [2.2 纯责任链与非纯责任链](#2.2 纯责任链与非纯责任链)
- [2.3 责任链模式 vs 策略模式](#2.3 责任链模式 vs 策略模式)
- [三、Spring 拦截器链](#三、Spring 拦截器链)
-
- [3.1 Spring 拦截器工作原理](#3.1 Spring 拦截器工作原理)
- [3.2 实战示例------自定义拦截器链](#3.2 实战示例——自定义拦截器链)
- 四、总结
一、概述
在软件开发中,经常会遇到这样的场景:一个请求需要经过多个处理环节,每个环节都有机会处理该请求,或者将其传递给下一个环节。例如,请假审批流程中,3 天以内由组长审批、3~7 天由经理审批、7 天以上由总监审批;Web 请求处理中,需要依次经过登录校验、权限校验、参数校验、日志记录等多个过滤器;订单处理中,需要依次经过库存检查、优惠计算、运费计算、支付处理等环节。如果将这些处理逻辑全部写在一个方法中,代码会变得臃肿、耦合度高、难以扩展:
if 条件1
if 条件2
if 条件3
if 条件4
if 条件5
客户端
巨型处理方法
登录校验
权限校验
参数校验
业务处理
日志记录
每新增一个处理步骤就要修改核心方法
责任链模式(Chain of Responsibility Pattern)正是为了解决这个问题而诞生的------它将请求的发送者和接收者解耦,使多个对象都有机会处理这个请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。发送者无需知道哪个对象会处理请求,接收者之间也无需知道彼此的存在。
生活中的责任链模式例子:
- 请假审批流程:员工请假 → 组长审批(3 天内)→ 经理审批(3~7 天)→ 总监审批(7 天以上),每个审批者只需判断自己能否处理,不能处理则交给上级
- 快递分拣:快递到达分拣中心 → A 区处理 → B 区处理 → C 区处理,快件沿着传送带依次经过各区域,直到被对应区域处理
- 击鼓传花:鼓声响起时花依次传递,鼓声停下时花在谁手里谁就"处理"它
- Web 中间件链:HTTP 请求依次经过认证中间件、授权中间件、限流中间件、日志中间件,每个中间件都可以拦截请求或放行
核心:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止
1.1 结构与角色
责任链模式包含以下角色:
next.handle()
继承
继承
继承
Client 客户端
Handler 抽象处理器
ConcreteHandlerA 具体处理器A
ConcreteHandlerB 具体处理器B
ConcreteHandlerC 具体处理器C
- Handler(抽象处理器):定义一个处理请求的接口(或抽象类),同时持有下一个处理器的引用,提供设置后继者的方法
- ConcreteHandler(具体处理器):实现抽象处理器,处理它所负责的请求;如果不能处理,则将请求转发给后继者
- Client(客户端):创建处理器对象,将它们组装成链,并向链头的处理器提交请求
- 链的组装顺序:客户端负责决定处理器的排列顺序,链上的处理器无需感知整条链的结构
1.2 适用场景
- 一个请求需要经过多个对象处理,且处理顺序可变、处理对象可动态增删
- 希望在不明确指定接收者的情况下,向多个对象中的一个提交请求
- 需要动态指定一组处理请求的对象,或者希望客户端无需了解具体的处理者
- 常见的框架场景:Servlet Filter 链、Spring Interceptor 链、Netty ChannelPipeline、审批工作流等
二、实现方式
责任链模式的核心实现思路是:每个处理器持有一个指向后继处理器的引用,在 handle() 方法中判断自身能否处理,能处理则处理,不能处理则调用 next.handle() 将请求沿链传递。
2.1 基础实现
以"请假审批"为例,员工提交请假申请,根据请假天数由不同级别的管理者审批:
- 组长:可审批 3 天以内的请假
- 经理:可审批 3~7 天的请假
- 总监:可审批 7 天以上的请假
next
next
继承
继承
继承
客户端
TeamLeader 组长
Manager 经理
Director 总监
Approver 抽象处理器
(1)抽象处理器------审批者
java
/**
* 抽象处理器:审批者
* 持有下一个审批者的引用,定义审批流程的模板
*/
public abstract class Approver {
/** 下一个审批者 */
protected Approver next;
/** 当前审批者的名称 */
protected final String name;
public Approver(String name) {
this.name = name;
}
/**
* 设置下一个审批者
*
* @param next 下一个审批者
* @return 下一个审批者(支持链式组装)
*/
public Approver setNext(Approver next) {
this.next = next;
return next;
}
/**
* 处理请假请求(模板方法)
* 如果能处理则处理,否则交给下一个审批者
*
* @param request 请假请求
*/
public void handleRequest(LeaveRequest request) {
if (canHandle(request)) {
System.out.println(name + " 审批通过【" + request.getApplicant()
+ "】的请假申请(" + request.getDays() + " 天)");
} else if (next != null) {
System.out.println(name + " 无法审批,转交 " + next.name);
next.handleRequest(request);
} else {
System.out.println("请假申请无法处理:无人可审批 " + request.getDays() + " 天的请假");
}
}
/**
* 判断是否能处理当前请求(由子类实现)
*
* @param request 请假请求
* @return 是否能处理
*/
protected abstract boolean canHandle(LeaveRequest request);
}
(2)请假请求实体
java
/**
* 请假请求
*/
public class LeaveRequest {
/** 申请人姓名 */
private final String applicant;
/** 请假天数 */
private final int days;
/** 请假原因 */
private final String reason;
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;
}
}
(3)具体处理器------组长
java
/**
* 具体处理器:组长
* 可审批 3 天以内的请假
*/
public class TeamLeader extends Approver {
private static final int MAX_DAYS = 3;
public TeamLeader(String name) {
super(name);
}
@Override
protected boolean canHandle(LeaveRequest request) {
return request.getDays() <= MAX_DAYS;
}
}
(4)具体处理器------经理
java
/**
* 具体处理器:经理
* 可审批 3~7 天的请假
*/
public class Manager extends Approver {
private static final int MAX_DAYS = 7;
public Manager(String name) {
super(name);
}
@Override
protected boolean canHandle(LeaveRequest request) {
return request.getDays() <= MAX_DAYS;
}
}
(5)具体处理器------总监
java
/**
* 具体处理器:总监
* 可审批 7 天以上的请假
*/
public class Director extends Approver {
public Director(String name) {
super(name);
}
@Override
protected boolean canHandle(LeaveRequest request) {
// 总监可以审批任意天数的请假
return true;
}
}
(6)客户端调用
java
public class ChainDemo {
public static void main(String[] args) {
// 创建审批者
Approver teamLeader = new TeamLeader("张组长");
Approver manager = new Manager("李经理");
Approver director = new Director("王总监");
// 组装责任链:组长 → 经理 → 总监
teamLeader.setNext(manager).setNext(director);
// 提交请假申请
LeaveRequest req1 = new LeaveRequest("小明", 2, "感冒看病");
teamLeader.handleRequest(req1);
// 张组长 审批通过【小明】的请假申请(2 天)
LeaveRequest req2 = new LeaveRequest("小红", 5, "回老家参加婚礼");
teamLeader.handleRequest(req2);
// 张组长 无法审批,转交 李经理
// 李经理 审批通过【小红】的请假申请(5 天)
LeaveRequest req3 = new LeaveRequest("小刚", 10, "出国旅游");
teamLeader.handleRequest(req3);
// 张组长 无法审批,转交 李经理
// 李经理 无法审批,转交 王总监
// 王总监 审批通过【小刚】的请假申请(10 天)
}
}
关键点 :每个处理器只需关注自身能否处理当前请求------能处理则处理,不能处理则交给下一个。客户端只与链头交互,无需知道链上有多少处理器以及各自的分工。新增一个审批级别只需新增一个处理器类并插入链中,符合开闭原则。
2.2 纯责任链与非纯责任链
根据责任链中处理器对请求的"独占性",责任链分为两种模式:
| 对比维度 | 纯责任链 | 非纯责任链 |
|---|---|---|
| 处理方式 | 请求被链上某一个处理器处理 | 请求被链上多个处理器依次处理 |
| 传递控制 | 一旦处理,立即终止传递 | 处理完后继续传递给下一个 |
| 链尾行为 | 必须确保至少有一个处理器能处理 | 所有处理器都可能参与处理 |
| 典型场景 | 审批流程、异常捕获、击鼓传花 | Filter 链、Interceptor 链、中间件链 |
| 结构复杂度 | 简单 | 较复杂,需要控制流程 |
纯责任链(前面的请假审批示例)------请求只在链上传递,直到有一个处理器处理它:
不能处理
不能处理
处理!
请求
处理器A
处理器B
处理器C
终止
非纯责任链------请求会依次经过链上的每一个处理器,每个处理器都可能对请求进行处理(或增强):
处理后传递
处理后传递
处理后传递
请求
处理器A
处理器B
处理器C
响应
以"敏感词过滤"为例演示非纯责任链------一条消息需要依次经过多个过滤器,每个过滤器负责过滤一类敏感词:
(1)处理器接口(非纯责任链,每个处理器都处理)
java
/**
* 过滤器接口(非纯责任链)
* 每个过滤器对消息进行过滤处理,处理完后继续传递
*/
public interface Filter {
/**
* 过滤消息
*
* @param msg 原始消息
* @param chain 过滤器链
* @return 过滤后的消息
*/
String doFilter(String msg, FilterChain chain);
}
(2)过滤器链管理
java
import java.util.ArrayList;
import java.util.List;
/**
* 过滤器链(非纯责任链)
* 管理所有过滤器,按顺序依次调用
*/
public class FilterChain {
private final List<Filter> filters = new ArrayList<>();
private int index = 0;
/**
* 添加过滤器到链中
*
* @param filter 过滤器
* @return 当前链(支持链式添加)
*/
public FilterChain addFilter(Filter filter) {
filters.add(filter);
return this;
}
/**
* 执行过滤链
* 按索引顺序依次调用每个过滤器
*
* @param msg 原始消息
* @return 过滤后的消息
*/
public String doFilter(String msg) {
if (index < filters.size()) {
Filter filter = filters.get(index++);
return filter.doFilter(msg, this);
}
return msg;
}
/**
* 重置索引(支持链的复用)
*/
public void reset() {
index = 0;
}
}
(3)具体过滤器------政治敏感词过滤
java
/**
* 具体过滤器:政治敏感词过滤
*/
public class PoliticalFilter implements Filter {
@Override
public String doFilter(String msg, FilterChain chain) {
// 过滤敏感词
msg = msg.replace("敏感词A", "***")
.replace("敏感词B", "***");
// 处理完后继续传递给下一个过滤器
return chain.doFilter(msg);
}
}
(4)具体过滤器------色情敏感词过滤
java
/**
* 具体过滤器:色情敏感词过滤
*/
public class PornFilter implements Filter {
@Override
public String doFilter(String msg, FilterChain chain) {
msg = msg.replace("色情词A", "***")
.replace("色情词B", "***");
return chain.doFilter(msg);
}
}
(5)具体过滤器------广告敏感词过滤
java
/**
* 具体过滤器:广告敏感词过滤
*/
public class AdFilter implements Filter {
@Override
public String doFilter(String msg, FilterChain chain) {
msg = msg.replace("广告词A", "***")
.replace("广告词B", "***");
return chain.doFilter(msg);
}
}
(6)客户端调用
java
public class FilterChainDemo {
public static void main(String[] args) {
// 创建过滤器链(非纯责任链)
FilterChain chain = new FilterChain();
chain.addFilter(new PoliticalFilter())
.addFilter(new PornFilter())
.addFilter(new AdFilter());
String msg = "这是一条包含敏感词A和色情词A以及广告词A的消息";
String result = chain.doFilter(msg);
System.out.println("过滤前:" + msg);
System.out.println("过滤后:" + result);
// 过滤前:这是一条包含敏感词A和色情词A以及广告词A的消息
// 过滤后:这是一条包含***和***以及***的消息
}
}
纯 vs 非纯的选择 :审批类场景(一个请求只需一个处理器处理)使用纯责任链;过滤/增强类场景(请求需要被多个处理器依次处理)使用非纯责任链。实际开发中非纯责任链的使用频率更高,因为大多数框架(Servlet Filter、Spring Interceptor)都采用这种模式。
2.3 责任链模式 vs 策略模式
责任链模式和策略模式都涉及"多个处理方式的选择",容易混淆:
| 对比维度 | 责任链模式 | 策略模式 |
|---|---|---|
| 核心目的 | 让多个对象依次尝试处理请求 | 从多个算法中显式选择一个执行 |
| 处理者数量 | 可能有多个处理器参与 | 每次只有一个策略执行 |
| 选择方式 | 处理器自行判断能否处理 | 客户端显式选择策略 |
| 客户端感知 | 客户端不知道谁会处理 | 客户端明确知道使用哪个策略 |
| 链结构 | 有明确的链式结构(next 引用) | 无链式结构(Context 直接持有 Strategy) |
| 典型场景 | 审批流程、过滤器链、异常捕获 | 支付方式、排序算法、出行方式 |
简单记忆 :责任链是"我处理不了,你来 ",策略是"我选择用哪个算法"。前者是被动的尝试,后者是主动的选择。
三、Spring 拦截器链
Spring 框架中的拦截器(Interceptor)是责任链模式在 Java Web 开发中最经典、最常用的应用。Spring MVC 的拦截器链采用非纯责任链 模式------请求依次经过每个拦截器的 preHandle(),任何一个拦截器返回 false 都会截断链的传递。
3.1 Spring 拦截器工作原理
Spring MVC 拦截器基于 HandlerInterceptor 接口,提供了三个拦截点:
返回 true
返回 true
返回 true
返回 false 截断
客户端请求
DispatcherServlet
preHandle-1 拦截器1
preHandle-2 拦截器2
preHandle-3 拦截器3
Controller 处理器
postHandle-3
postHandle-2
postHandle-1
afterCompletion-3
afterCompletion-2
afterCompletion-1
响应返回客户端
直接返回
- preHandle :在 Controller 方法执行前调用,返回
true放行,返回false截断链 - postHandle:在 Controller 方法执行后、视图渲染前调用
- afterCompletion:在整个请求完成后(视图渲染后)调用,通常用于资源清理
Spring 内部通过 HandlerExecutionChain 管理拦截器链:
java
// Spring 源码简化示意
public class HandlerExecutionChain {
private final Object handler;
@Nullable
private HandlerInterceptor[] interceptors;
// 按注册顺序依次调用 preHandle
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response)
throws Exception {
for (int i = 0; i < this.interceptors.length; i++) {
HandlerInterceptor interceptor = this.interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
// 任何一个拦截器返回 false,触发已执行拦截器的 afterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
}
return true;
}
}
这就是典型的非纯责任链------请求依次经过每个拦截器,每个拦截器都有机会处理(增强/拦截)请求,处理完后传递给下一个。
3.2 实战示例------自定义拦截器链
以"API 请求防护"为例,设计三个拦截器:登录校验、权限校验、限流校验,请求必须依次通过这三个拦截器才能到达 Controller。
(1)登录校验拦截器
java
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 登录校验拦截器
* 检查请求是否携带有效 token,未登录则拦截
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(401);
response.getWriter().write("{\"code\":401,\"msg\":\"请先登录\"}");
// 返回 false 截断链,后续拦截器不再执行
return false;
}
// 模拟 token 校验
if (!isValidToken(token)) {
response.setStatus(401);
response.getWriter().write("{\"code\":401,\"msg\":\"token 已过期\"}");
return false;
}
System.out.println("LoginInterceptor:登录校验通过");
// 返回 true 放行,传递给下一个拦截器
return true;
}
private boolean isValidToken(String token) {
// 实际项目中调用认证服务校验 token
return token.startsWith("Bearer ");
}
}
(2)权限校验拦截器
java
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 权限校验拦截器
* 检查用户是否有访问当前接口的权限
*/
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 从请求中获取用户角色(实际项目中从 token 或 session 中获取)
String role = request.getHeader("X-User-Role");
String uri = request.getRequestURI();
// 管理员接口只允许 admin 角色访问
if (uri.startsWith("/admin") && !"admin".equals(role)) {
response.setStatus(403);
response.getWriter().write("{\"code\":403,\"msg\":\"权限不足\"}");
return false;
}
System.out.println("PermissionInterceptor:权限校验通过(角色:" + role + ")");
return true;
}
}
(3)限流校验拦截器
java
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 限流校验拦截器
* 基于滑动窗口的简单限流,限制每个 IP 每秒的请求次数
*/
public class RateLimitInterceptor implements HandlerInterceptor {
/** IP → 请求计数器 */
private final Map<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();
/** 每秒最大请求数 */
private static final int MAX_REQUESTS_PER_SECOND = 10;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String clientIp = getClientIp(request);
AtomicInteger counter = counterMap.computeIfAbsent(
clientIp, k -> new AtomicInteger(0));
int currentCount = counter.incrementAndGet();
if (currentCount > MAX_REQUESTS_PER_SECOND) {
response.setStatus(429);
response.getWriter().write("{\"code\":429,\"msg\":\"请求过于频繁,请稍后再试\"}");
return false;
}
System.out.println("RateLimitInterceptor:限流通过(IP:" + clientIp
+ ",请求数:" + currentCount + ")");
return true;
}
/**
* 获取客户端真实 IP
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty()) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 请求完成后递减计数器(afterCompletion 中调用)
*/
public void decrementCounter(String clientIp) {
AtomicInteger counter = counterMap.get(clientIp);
if (counter != null) {
counter.decrementAndGet();
}
}
}
(4)注册拦截器
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Spring MVC 配置------注册拦截器链
* 拦截器按注册顺序执行,形成责任链
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**") // 拦截所有 API 请求
.excludePathPatterns("/api/login", "/api/register") // 排除登录注册
.order(1); // 最先执行
registry.addInterceptor(new PermissionInterceptor())
.addPathPatterns("/api/**")
.order(2); // 第二执行
registry.addInterceptor(new RateLimitInterceptor())
.addPathPatterns("/api/**")
.order(3); // 第三执行
}
}
关键点 :Spring 拦截器的执行顺序由
order()值决定(值越小越先执行),这与责任链模式中客户端控制链组装顺序的思想完全一致。拦截器的preHandle()返回false时会截断链的传递------这正是责任链模式中"某个处理器处理完毕,不再向后传递"的体现。
四、总结
责任链模式是行为型设计模式中使用频率极高的模式之一,它将请求的发送者和接收者解耦,使多个处理器都有机会处理请求。本文围绕责任链模式的核心思想,从结构、实现到框架应用进行了系统讲解:
| 章节 | 核心内容 |
|---|---|
| 一、概述 | 介绍了责任链模式的定义、结构和适用场景,通过请假审批、快递分拣等生活场景帮助理解 |
| 二、实现方式 | 以请假审批为例演示了纯责任链,以敏感词过滤为例演示了非纯责任链,并对比了两种模式的区别 |
| 三、Spring 拦截器链 | 深入分析了 Spring MVC 拦截器链的责任链本质,提供了登录校验、权限校验、限流校验的完整实战示例 |
责任链模式的核心优势:
- 解耦发送者与接收者:客户端只需将请求提交给链头,无需知道链上有哪些处理器以及处理规则
- 动态组装链:可以在运行时动态增删处理器、调整处理顺序,极大提升灵活性
- 单一职责:每个处理器只关注自己的处理逻辑,职责清晰,便于维护和测试
- 符合开闭原则:新增处理器无需修改已有处理器和客户端代码
| 优点 | 缺点 |
|---|---|
| 降低耦合度,发送者与接收者解耦 | 链过长时可能影响性能 |
| 增强灵活性,支持动态增删处理器 | 调试困难,请求在链上的流转路径不直观 |
| 符合单一职责原则,每个处理器职责清晰 | 纯责任链中可能没有处理器处理请求(链尾需兜底) |
| 符合开闭原则,新增处理器无需修改已有代码 | 链的组装顺序对结果有决定性影响,需谨慎设计 |
在实际开发中,责任链模式广泛应用于:Servlet Filter 链、Spring Interceptor 链、Netty ChannelPipeline、日志框架(Logback/SLF4J)的 Appender 链、审批工作流引擎等。掌握责任链模式,不仅能提升代码的扩展性和可维护性,还能深入理解各类框架的底层设计思想。