一、责任链模式核心定义
责任链模式是行为型设计模式的一种,核心目的是:
为请求创建一个接收者对象的链,使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。请求沿着链条传递,直到链上的某个对象处理它为止。
简单来说,就是 "流水线处理请求":一个请求过来,先交给第一个处理器,能处理就处理,不能处理就传给下一个,直到有处理器处理或链条结束。
核心角色
| 角色 | 作用 |
|---|---|
| 抽象处理器(Handler) | 定义处理请求的接口,包含一个指向下一个处理器的引用(形成链条) |
| 具体处理器(ConcreteHandler) | 实现抽象处理器接口,判断是否能处理当前请求:能处理则处理,不能则传递给下一个处理器 |
| 客户端(Client) | 创建处理器链条,发起请求(只需将请求交给链条的第一个处理器) |
生活类比(最易理解)
- 场景 1 :公司报销审批
- 抽象处理器:审批流程接口;
- 具体处理器:实习生(≤500 元)→ 主管(≤5000 元)→ 经理(≤50000 元)→ 老板(>50000 元);
- 流程:5000 元报销单 → 实习生(处理不了)→ 主管(能处理)→ 审批通过。
- 场景 2 :电商订单退款
- 具体处理器:客服(≤100 元)→ 退款专员(≤1000 元)→ 退款主管(>1000 元);
- 流程:800 元退款 → 客服→退款专员→处理完成。
二、实战案例 1:基础版(报销审批流程)
先实现一个极简的责任链,理解核心逻辑:
1. 抽象处理器:审批接口
/**
* 抽象处理器:报销审批接口
*/
public abstract class ApproverHandler {
// 下一个处理器(形成链条)
protected ApproverHandler nextHandler;
/**
* 设置下一个处理器(构建链条)
*/
public ApproverHandler setNextHandler(ApproverHandler nextHandler) {
this.nextHandler = nextHandler;
return nextHandler; // 链式调用,方便构建链条
}
/**
* 处理报销请求
* @param amount 报销金额
* @return 审批结果
*/
public abstract String approve(double amount);
}
2. 具体处理器:各级审批人
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 具体处理器1:实习生(处理≤500元)
*/
public class InternApprover extends ApproverHandler {
private static final Logger log = LoggerFactory.getLogger(InternApprover.class);
private static final double MAX_AMOUNT = 500.0;
@Override
public String approve(double amount) {
if (amount <= MAX_AMOUNT) {
String result = String.format("实习生审批通过 | 报销金额:%.2f元", amount);
log.info(result);
return result;
} else {
log.info("实习生无权限处理(金额>500元),转交主管");
// 传递给下一个处理器
return nextHandler != null ? nextHandler.approve(amount) : "审批链条中断,无人处理";
}
}
}
/**
* 具体处理器2:主管(处理≤5000元)
*/
public class SupervisorApprover extends ApproverHandler {
private static final Logger log = LoggerFactory.getLogger(SupervisorApprover.class);
private static final double MAX_AMOUNT = 5000.0;
@Override
public String approve(double amount) {
if (amount <= MAX_AMOUNT) {
String result = String.format("主管审批通过 | 报销金额:%.2f元", amount);
log.info(result);
return result;
} else {
log.info("主管无权限处理(金额>5000元),转交经理");
return nextHandler != null ? nextHandler.approve(amount) : "审批链条中断,无人处理";
}
}
}
/**
* 具体处理器3:经理(处理≤50000元)
*/
public class ManagerApprover extends ApproverHandler {
private static final Logger log = LoggerFactory.getLogger(ManagerApprover.class);
private static final double MAX_AMOUNT = 50000.0;
@Override
public String approve(double amount) {
if (amount <= MAX_AMOUNT) {
String result = String.format("经理审批通过 | 报销金额:%.2f元", amount);
log.info(result);
return result;
} else {
log.info("经理无权限处理(金额>50000元),转交老板");
return nextHandler != null ? nextHandler.approve(amount) : "审批链条中断,无人处理";
}
}
}
/**
* 具体处理器4:老板(处理所有金额)
*/
public class BossApprover extends ApproverHandler {
private static final Logger log = LoggerFactory.getLogger(BossApprover.class);
@Override
public String approve(double amount) {
String result = String.format("老板审批通过 | 报销金额:%.2f元", amount);
log.info(result);
return result;
}
}
3. 客户端:构建链条并发起请求
/**
* 客户端:测试报销审批责任链
*/
public class ApprovalClient {
public static void main(String[] args) {
// 1. 创建各级处理器
ApproverHandler intern = new InternApprover();
ApproverHandler supervisor = new SupervisorApprover();
ApproverHandler manager = new ManagerApprover();
ApproverHandler boss = new BossApprover();
// 2. 构建责任链:实习生 → 主管 → 经理 → 老板
intern.setNextHandler(supervisor)
.setNextHandler(manager)
.setNextHandler(boss);
// 3. 发起不同金额的报销请求
System.out.println("======= 测试300元报销 =======");
intern.approve(300.0);
System.out.println("\n======= 测试3000元报销 =======");
intern.approve(3000.0);
System.out.println("\n======= 测试30000元报销 =======");
intern.approve(30000.0);
System.out.println("\n======= 测试80000元报销 =======");
intern.approve(80000.0);
}
}
输出结果
======= 测试300元报销 =======
实习生审批通过 | 报销金额:300.00元
======= 测试3000元报销 =======
实习生无权限处理(金额>500元),转交主管
主管审批通过 | 报销金额:3000.00元
======= 测试30000元报销 =======
实习生无权限处理(金额>500元),转交主管
主管无权限处理(金额>5000元),转交经理
经理审批通过 | 报销金额:30000.00元
======= 测试80000元报销 =======
实习生无权限处理(金额>500元),转交主管
主管无权限处理(金额>5000元),转交经理
经理无权限处理(金额>50000元),转交老板
老板审批通过 | 报销金额:80000.00元
三、实战案例 2:Spring 实战版(接口请求参数校验)
在真实的 Spring 项目中,责任链模式常用于接口参数校验、请求过滤、权限校验等场景。以下实现一个 "接口请求参数校验责任链",支持可配置的校验规则,贴合生产环境。
1. 依赖准备(Maven)
<dependencies>
<!-- Spring Boot核心 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.3</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
</dependencies>
2. 基础模型定义
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 接口请求参数
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiRequest {
private String userId; // 用户ID
private String token; // 登录令牌
private String productId; // 商品ID
private int quantity; // 购买数量
private double amount; // 金额
}
/**
* 校验结果
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ValidateResult {
private boolean success; // 是否通过
private String message; // 提示信息
// 快捷创建方法
public static ValidateResult pass() {
return new ValidateResult(true, "校验通过");
}
public static ValidateResult fail(String message) {
return new ValidateResult(false, message);
}
}
3. 抽象处理器(Spring 管理)
import org.springframework.core.Ordered;
/**
* 抽象处理器:请求参数校验处理器
* 实现Ordered接口,支持按顺序自动组装链条
*/
public abstract class AbstractValidateHandler implements Ordered {
// 下一个处理器
protected AbstractValidateHandler nextHandler;
/**
* 设置下一个处理器
*/
public void setNextHandler(AbstractValidateHandler nextHandler) {
this.nextHandler = nextHandler;
}
/**
* 处理校验请求
*/
public abstract ValidateResult validate(ApiRequest request);
/**
* 执行下一个处理器(通用方法)
*/
protected ValidateResult doNext(ApiRequest request) {
return nextHandler != null ? nextHandler.validate(request) : ValidateResult.pass();
}
}
4. 具体处理器(Spring Bean)
import org.springframework.stereotype.Component;
/**
* 具体处理器1:登录令牌校验
*/
@Component
public class TokenValidateHandler extends AbstractValidateHandler {
@Override
public ValidateResult validate(ApiRequest request) {
// 校验逻辑:token非空且长度≥16
if (request.getToken() == null || request.getToken().length() < 16) {
return ValidateResult.fail("token无效:不能为空且长度≥16");
}
// 校验通过,传递给下一个处理器
return doNext(request);
}
@Override
public int getOrder() {
return 1; // 优先级:数字越小越先执行
}
}
/**
* 具体处理器2:用户ID校验
*/
@Component
public class UserIdValidateHandler extends AbstractValidateHandler {
@Override
public ValidateResult validate(ApiRequest request) {
// 校验逻辑:userId非空且以U开头
if (request.getUserId() == null || !request.getUserId().startsWith("U")) {
return ValidateResult.fail("userId无效:不能为空且以U开头");
}
return doNext(request);
}
@Override
public int getOrder() {
return 2;
}
}
/**
* 具体处理器3:商品ID校验
*/
@Component
public class ProductIdValidateHandler extends AbstractValidateHandler {
@Override
public ValidateResult validate(ApiRequest request) {
// 校验逻辑:productId非空且以P开头
if (request.getProductId() == null || !request.getProductId().startsWith("P")) {
return ValidateResult.fail("productId无效:不能为空且以P开头");
}
return doNext(request);
}
@Override
public int getOrder() {
return 3;
}
}
/**
* 具体处理器4:数量校验
*/
@Component
public class QuantityValidateHandler extends AbstractValidateHandler {
@Override
public ValidateResult validate(ApiRequest request) {
// 校验逻辑:数量>0且≤100
if (request.getQuantity() <= 0 || request.getQuantity() > 100) {
return ValidateResult.fail("数量无效:必须大于0且≤100");
}
return doNext(request);
}
@Override
public int getOrder() {
return 4;
}
}
/**
* 具体处理器5:金额校验
*/
@Component
public class AmountValidateHandler extends AbstractValidateHandler {
@Override
public ValidateResult validate(ApiRequest request) {
// 校验逻辑:金额>0
if (request.getAmount() <= 0) {
return ValidateResult.fail("金额无效:必须大于0");
}
return doNext(request);
}
@Override
public int getOrder() {
return 5;
}
}
5. 责任链管理器(自动组装链条)
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/**
* 责任链管理器:自动组装校验处理器链条
*/
@Component
public class ValidateChainManager implements InitializingBean {
// 注入所有校验处理器(Spring自动扫描)
@Autowired
private List<AbstractValidateHandler> validateHandlers;
// 链条的第一个处理器
private AbstractValidateHandler firstHandler;
/**
* 初始化时自动组装链条(按Order排序)
*/
@Override
public void afterPropertiesSet() {
// 1. 按Order排序
List<AbstractValidateHandler> sortedHandlers = validateHandlers.stream()
.sorted((h1, h2) -> h1.getOrder() - h2.getOrder())
.collect(Collectors.toList());
// 2. 组装链条
for (int i = 0; i < sortedHandlers.size() - 1; i++) {
sortedHandlers.get(i).setNextHandler(sortedHandlers.get(i + 1));
}
// 3. 设置第一个处理器
if (!sortedHandlers.isEmpty()) {
firstHandler = sortedHandlers.get(0);
}
}
/**
* 执行校验(对外统一入口)
*/
public ValidateResult validate(ApiRequest request) {
if (firstHandler == null) {
return ValidateResult.pass();
}
return firstHandler.validate(request);
}
}
6. 客户端(Spring Boot 测试)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 客户端:测试Spring版责任链
*/
@SpringBootApplication
public class SpringChainDemoApplication {
public static void main(String[] args) {
// 1. 启动Spring容器
ConfigurableApplicationContext context = SpringApplication.run(SpringChainDemoApplication.class, args);
ValidateChainManager chainManager = context.getBean(ValidateChainManager.class);
// 2. 测试1:参数全部正确
System.out.println("======= 测试1:参数全部正确 =======");
ApiRequest request1 = new ApiRequest(
"U10086",
"token1234567890123456",
"P8888",
10,
999.9
);
ValidateResult result1 = chainManager.validate(request1);
System.out.println("校验结果:" + result1);
// 3. 测试2:token无效
System.out.println("\n======= 测试2:token无效 =======");
ApiRequest request2 = new ApiRequest(
"U10086",
"shortToken", // 长度不足
"P8888",
10,
999.9
);
ValidateResult result2 = chainManager.validate(request2);
System.out.println("校验结果:" + result2);
// 4. 测试3:数量无效
System.out.println("\n======= 测试3:数量无效 =======");
ApiRequest request3 = new ApiRequest(
"U10086",
"token1234567890123456",
"P8888",
0, // 数量为0
999.9
);
ValidateResult result3 = chainManager.validate(request3);
System.out.println("校验结果:" + result3);
context.close();
}
}
输出结果
======= 测试1:参数全部正确 =======
校验结果:ValidateResult(success=true, message=校验通过)
======= 测试2:token无效 =======
校验结果:ValidateResult(success=false, message=token无效:不能为空且长度≥16)
======= 测试3:数量无效 =======
校验结果:ValidateResult(success=false, message=数量无效:必须大于0且≤100)
四、责任链模式的典型实战场景
1. Spring Interceptor(拦截器链)
- Spring MVC 的HandlerInterceptor是典型的责任链:
- 抽象处理器:HandlerInterceptor;
- 具体处理器:自定义拦截器(如登录拦截、日志拦截、权限拦截);
- 链条组装:InterceptorRegistry按顺序添加拦截器,请求依次经过所有拦截器。
2. Servlet Filter(过滤器链)
- Java Web 的Filter链:
- 抽象处理器:Filter;
- 具体处理器:字符编码过滤器、XSS 过滤器、跨域过滤器;
- 链条传递:chain.doFilter(request, response)将请求传递给下一个过滤器。
3. MyBatis 插件(Interceptor 链)
- MyBatis 的插件机制基于责任链:
- 抽象处理器:Interceptor;
- 具体处理器:分页插件、性能监控插件、SQL 加密插件;
- 链条执行:插件按顺序拦截 SQL 执行流程。
4. 接口请求处理(如网关过滤)
- 网关(Gateway/Nginx)的过滤链:
- 具体处理器:限流过滤器、黑白名单过滤器、参数校验过滤器、日志过滤器;
- 核心:请求依次经过过滤器,任意过滤器拒绝则直接返回。
五、责任链模式的关键注意事项
1. 链条构建方式
- 手动构建 :如基础版案例,客户端手动设置
nextHandler; - 自动构建 :如 Spring 版案例,通过
Ordered排序,容器初始化时自动组装; - 注解驱动 :通过自定义注解(如
@HandlerOrder)指定顺序。
2. 终止条件
- 正常终止:某个处理器处理请求后,不再传递(如校验失败直接返回);
- 完整执行:所有处理器都执行一遍(如日志、监控类处理器);
- 异常终止:处理器抛出异常,链条中断。
3. 优缺点
| 优点 | 缺点 |
|---|---|
| 解耦请求发送者和接收者 | 链条过长会影响性能(需控制处理器数量) |
| 新增处理器无需修改原有代码(开闭原则) | 调试复杂(需跟踪链条执行流程) |
| 可灵活调整处理器顺序 | 可能出现请求未被处理的情况 |
六、责任链模式与装饰器模式的区别(易混淆点)
| 特性 | 责任链模式 | 装饰器模式 |
|---|---|---|
| 核心目的 | 处理请求(谁能处理谁上) | 增强功能(层层增强) |
| 传递逻辑 | 可选传递(处理不了才传) | 必须传递(增强后调用目标) |
| 处理器关系 | 平等的处理者 | 层层包裹的增强者 |
| 典型场景 | 校验、过滤、审批 | 功能增强(日志、缓存) |
总结
-
责任链模式的核心是请求沿处理器链条传递,直到被处理或链条结束,核心价值是解耦请求发送者和接收者,支持灵活扩展;
-
实战中常用两种构建方式:手动构建(简单场景)、Spring 自动组装(生产场景);
-
Spring 的 Interceptor、Servlet 的 Filter、MyBatis 的插件都是责任链模式的经典应用,掌握它能理解框架的核心设计思想。
扩展
基于 Spring Boot 实现可直接运行的项目,清晰拆解拦截器链的注册、执行顺序、请求传递逻辑,贴合生产环境的登录鉴权 + 接口权限控制场景。
一、核心原理回顾
Spring MVC 的HandlerInterceptor是责任链模式的典型实现:
- 抽象处理器 :
HandlerInterceptor接口(定义preHandle/postHandle/afterCompletion三个方法); - 具体处理器:自定义登录拦截器(校验 token)、权限拦截器(校验接口访问权限);
- 链条组装 :通过
WebMvcConfigurer注册拦截器并指定执行顺序; - 请求传递 :请求依次经过所有拦截器的
preHandle(返回true则传递给下一个,false则终止)。
二、完整实战代码
1. 项目依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-mvc-interceptor-chain</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-mvc-interceptor-chain</name>
<dependencies>
<!-- Spring MVC核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JSON工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 步骤 1:定义核心模型和常量
package com.example.interceptor.constant;
/**
* 接口权限常量
*/
public interface PermissionConstant {
// 普通用户权限
String ROLE_USER = "USER";
// 管理员权限
String ROLE_ADMIN = "ADMIN";
}
package com.example.interceptor.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 统一返回结果
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
// 响应码:200成功,401未登录,403无权限,500异常
private int code;
private String msg;
private T data;
// 快捷创建方法
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static <T> Result<T> fail(int code, String msg) {
return new Result<>(code, msg, null);
}
// 常用失败场景
public static <T> Result<T> unLogin() {
return fail(401, "未登录,请先登录");
}
public static <T> Result<T> noPermission() {
return fail(403, "无权限访问,请联系管理员");
}
}
package com.example.interceptor.model;
import lombok.Data;
/**
* 登录用户信息(存储在ThreadLocal中)
*/
@Data
public class LoginUser {
private String userId;
private String username;
private String role; // 角色:USER/ADMIN
}
3. 步骤 2:自定义登录上下文(ThreadLocal 存储用户信息)
package com.example.interceptor.context;
import com.example.interceptor.model.LoginUser;
/**
* 登录上下文:存储当前请求的用户信息(ThreadLocal保证线程安全)
*/
public class LoginContext {
// ThreadLocal存储用户信息,每个线程独立
private static final ThreadLocal<LoginUser> USER_THREAD_LOCAL = new ThreadLocal<>();
/**
* 设置当前登录用户
*/
public static void setLoginUser(LoginUser user) {
USER_THREAD_LOCAL.set(user);
}
/**
* 获取当前登录用户
*/
public static LoginUser getLoginUser() {
return USER_THREAD_LOCAL.get();
}
/**
* 清除当前线程的用户信息(防止内存泄漏)
*/
public static void clear() {
USER_THREAD_LOCAL.remove();
}
}
4. 步骤 3:实现登录拦截器(第一个拦截器)
package com.example.interceptor.interceptor;
import com.alibaba.fastjson2.JSON;
import com.example.interceptor.context.LoginContext;
import com.example.interceptor.model.LoginUser;
import com.example.interceptor.model.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.PrintWriter;
/**
* 登录拦截器:校验token是否有效,解析用户信息
* 责任链第一个处理器:优先校验登录状态
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 前置处理:请求到达Controller前执行(核心拦截逻辑)
* @return true:通过拦截,继续传递;false:拦截终止,直接返回
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("======= 登录拦截器开始处理请求 =======");
String requestUri = request.getRequestURI();
log.info("请求URI:{},请求方式:{}", requestUri, request.getMethod());
// 1. 放行白名单接口(如登录接口)
if ("/api/user/login".equals(requestUri)) {
log.info("白名单接口,跳过登录校验");
return true;
}
// 2. 获取请求头中的token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
log.warn("请求头中无token,拦截请求");
// 返回未登录结果
returnJson(response, Result.unLogin());
return false;
}
// 3. 模拟校验token(真实场景从Redis/数据库查询)
LoginUser loginUser = validateToken(token);
if (loginUser == null) {
log.warn("token无效或已过期,拦截请求");
returnJson(response, Result.unLogin());
return false;
}
// 4. 将用户信息存入上下文,供后续拦截器/Controller使用
LoginContext.setLoginUser(loginUser);
log.info("登录校验通过,用户信息:{}", JSON.toJSONString(loginUser));
log.info("======= 登录拦截器处理完成,传递给下一个拦截器 =======");
return true;
}
/**
* 后置处理:Controller执行完成后执行(非必需)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清除ThreadLocal中的用户信息,防止内存泄漏
LoginContext.clear();
log.info("登录拦截器:清除当前线程用户信息");
}
/**
* 模拟token校验逻辑(真实场景替换为Redis查询)
*/
private LoginUser validateToken(String token) {
// 模拟有效token:admin_token → 管理员;user_token → 普通用户
if ("admin_token".equals(token)) {
LoginUser user = new LoginUser();
user.setUserId("U001");
user.setUsername("admin");
user.setRole("ADMIN");
return user;
} else if ("user_token".equals(token)) {
LoginUser user = new LoginUser();
user.setUserId("U002");
user.setUsername("user");
user.setRole("USER");
return user;
}
// 无效token返回null
return null;
}
/**
* 向响应中写入JSON结果
*/
private void returnJson(HttpServletResponse response, Result<?> result) throws Exception {
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(result));
writer.flush();
writer.close();
}
}
5. 步骤 4:实现权限拦截器(第二个拦截器)
package com.example.interceptor.interceptor;
import com.alibaba.fastjson2.JSON;
import com.example.interceptor.context.LoginContext;
import com.example.interceptor.model.LoginUser;
import com.example.interceptor.model.Result;
import com.example.interceptor.constant.PermissionConstant;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 权限拦截器:校验用户是否有接口访问权限
* 责任链第二个处理器:登录校验通过后,校验权限
*/
@Slf4j
public class PermissionInterceptor implements HandlerInterceptor {
/**
* 前置处理:校验接口权限
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("======= 权限拦截器开始处理请求 =======");
String requestUri = request.getRequestURI();
LoginUser loginUser = LoginContext.getLoginUser();
// 1. 获取用户角色
String userRole = loginUser.getRole();
log.info("当前用户角色:{},请求接口:{}", userRole, requestUri);
// 2. 权限校验逻辑
// 管理员可访问所有接口,普通用户仅能访问/user开头的接口
if (PermissionConstant.ROLE_ADMIN.equals(userRole)) {
log.info("管理员角色,拥有所有接口访问权限");
log.info("======= 权限拦截器处理完成,放行请求 =======");
return true;
}
// 普通用户权限校验
if (PermissionConstant.ROLE_USER.equals(userRole)) {
if (requestUri.startsWith("/api/user/")) {
log.info("普通用户访问用户接口,权限校验通过");
log.info("======= 权限拦截器处理完成,放行请求 =======");
return true;
} else {
log.warn("普通用户尝试访问管理员接口,拦截请求");
// 返回无权限结果
LoginInterceptor loginInterceptor = new LoginInterceptor();
loginInterceptor.returnJson(response, Result.noPermission());
return false;
}
}
// 未知角色,拦截
log.warn("未知用户角色,拦截请求");
LoginInterceptor loginInterceptor = new LoginInterceptor();
loginInterceptor.returnJson(response, Result.noPermission());
return false;
}
}
6. 步骤 5:注册拦截器链(配置执行顺序)
package com.example.interceptor.config;
import com.example.interceptor.interceptor.LoginInterceptor;
import com.example.interceptor.interceptor.PermissionInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置:注册拦截器链,指定执行顺序和拦截路径
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 注册拦截器链
* 执行顺序:先执行LoginInterceptor,再执行PermissionInterceptor
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 1. 注册登录拦截器
registry.addInterceptor(new LoginInterceptor())
// 拦截所有接口
.addPathPatterns("/api/**")
// 放行登录接口(白名单)
.excludePathPatterns("/api/user/login");
// 2. 注册权限拦截器(执行顺序在登录拦截器之后)
registry.addInterceptor(new PermissionInterceptor())
// 只拦截需要权限校验的接口
.addPathPatterns("/api/user/**", "/api/admin/**");
}
}
7. 步骤 6:实现测试 Controller
package com.example.interceptor.controller;
import com.example.interceptor.context.LoginContext;
import com.example.interceptor.model.LoginUser;
import com.example.interceptor.model.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户接口Controller
*/
@RestController
@RequestMapping("/api/user")
public class UserController {
/**
* 登录接口(白名单,跳过登录拦截)
*/
@PostMapping("/login")
public Result<String> login(String username, String password) {
// 模拟登录逻辑:admin/admin → 管理员token;user/user → 普通用户token
if ("admin".equals(username) && "admin".equals(password)) {
return Result.success("admin_token");
} else if ("user".equals(username) && "user".equals(password)) {
return Result.success("user_token");
} else {
return Result.fail(500, "用户名或密码错误");
}
}
/**
* 普通用户接口(需登录+USER权限)
*/
@GetMapping("/info")
public Result<LoginUser> getUserInfo() {
// 从上下文获取当前登录用户
LoginUser loginUser = LoginContext.getLoginUser();
return Result.success(loginUser);
}
}
package com.example.interceptor.controller;
import com.example.interceptor.model.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 管理员接口Controller
*/
@RestController
@RequestMapping("/api/admin")
public class AdminController {
/**
* 管理员接口(需登录+ADMIN权限)
*/
@GetMapping("/dashboard")
public Result<String> getAdminDashboard() {
return Result.success("管理员控制台数据:用户总数1000,订单总数5000");
}
}
8. 步骤 7:启动类
package com.example.interceptor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringMvcInterceptorChainApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMvcInterceptorChainApplication.class, args);
}
}
三、测试验证(Postman/Curl)
测试 1:访问登录接口(白名单,无需 token)
POST http://localhost:8080/api/user/login
参数:username=admin&password=admin
返回:{"code":200,"msg":"操作成功","data":"admin_token"}
POST http://localhost:8080/api/user/login
参数:username=user&password=user
返回:{"code":200,"msg":"操作成功","data":"user_token"}
测试 2:未登录访问用户接口(被登录拦截器拦截)
GET http://localhost:8080/api/user/info
请求头:无token
返回:{"code":401,"msg":"未登录,请先登录","data":null}
测试 3:普通用户访问用户接口(登录 + 权限都通过)
GET http://localhost:8080/api/user/info
请求头:token=user_token
返回:{"code":200,"msg":"操作成功","data":{"userId":"U002","username":"user","role":"USER"}}
测试 4:普通用户访问管理员接口(被权限拦截器拦截)
GET http://localhost:8080/api/admin/dashboard
请求头:token=user_token
返回:{"code":403,"msg":"无权限访问,请联系管理员","data":null}
测试 5:管理员访问所有接口(全部通过)
# 访问用户接口
GET http://localhost:8080/api/user/info
请求头:token=admin_token
返回:{"code":200,"msg":"操作成功","data":{"userId":"U001","username":"admin","role":"ADMIN"}}
# 访问管理员接口
GET http://localhost:8080/api/admin/dashboard
请求头:token=admin_token
返回:{"code":200,"msg":"操作成功","data":"管理员控制台数据:用户总数1000,订单总数5000"}
四、拦截器链核心细节拆解
1. 执行顺序
请求 → LoginInterceptor.preHandle(登录校验)→ PermissionInterceptor.preHandle(权限校验)→ Controller → PermissionInterceptor.postHandle → LoginInterceptor.postHandle → PermissionInterceptor.afterCompletion → LoginInterceptor.afterCompletion → 响应
preHandle:按注册顺序执行(先登录拦截,后权限拦截);postHandle/afterCompletion:按注册逆序执行(先权限拦截,后登录拦截)。
2. 链条终止条件
- 任意拦截器的
preHandle返回false,链条立即终止,后续拦截器和 Controller 都不会执行; - 示例:未登录时,LoginInterceptor 返回
false,PermissionInterceptor 不会执行。
3. ThreadLocal 的使用注意事项
- 必须在
afterCompletion中调用remove()清除 ThreadLocal,否则会导致内存泄漏; afterCompletion无论请求是否成功都会执行,是清理资源的最佳时机。
4. 白名单配置
- 通过
excludePathPatterns配置无需拦截的接口(如登录、注册、静态资源); - 支持通配符:
/api/user/**(拦截所有 user 子接口)、/api/*(拦截一级接口)。
五、生产级扩展建议
1. 权限配置优化
- 将权限规则配置到数据库 / 配置文件(如接口→角色映射),避免硬编码;
- 结合 Spring Security 的
@PreAuthorize注解,实现细粒度权限控制。
2. 拦截器通用化
- 提取拦截器基类,封装通用逻辑(如返回 JSON、日志记录);
- 通过注解标记需要权限的接口(如
@RequirePermission("ADMIN"))。
3. 性能优化
- 缓存权限规则(如加载到本地 Map),避免每次请求查询数据库;
- 对静态资源(JS/CSS/ 图片)完全放行,减少拦截器执行次数。
4. 异常处理
- 全局异常处理器(
@ControllerAdvice)捕获拦截器中未处理的异常; - 统一异常返回格式,避免前端解析失败。
总结
- Spring MVC 拦截器链是责任链模式的经典应用,核心是通过
HandlerInterceptor定义处理器,WebMvcConfigurer注册并指定执行顺序; - 登录拦截器 + 权限拦截器是最常见的拦截器链组合,实现 "先登录校验,后权限校验" 的流水线处理;
- 关键要点:
preHandle返回false终止链条、ThreadLocal 清理防止内存泄漏、白名单放行无需拦截的接口。
