【从零入门23种设计模式13】行为型之责任链模式

一、责任链模式核心定义

责任链模式是行为型设计模式的一种,核心目的是:

为请求创建一个接收者对象的链,使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。请求沿着链条传递,直到链上的某个对象处理它为止。

简单来说,就是 "流水线处理请求":一个请求过来,先交给第一个处理器,能处理就处理,不能处理就传给下一个,直到有处理器处理或链条结束。

核心角色
角色 作用
抽象处理器(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. 优缺点
优点 缺点
解耦请求发送者和接收者 链条过长会影响性能(需控制处理器数量)
新增处理器无需修改原有代码(开闭原则) 调试复杂(需跟踪链条执行流程)
可灵活调整处理器顺序 可能出现请求未被处理的情况

六、责任链模式与装饰器模式的区别(易混淆点)

特性 责任链模式 装饰器模式
核心目的 处理请求(谁能处理谁上) 增强功能(层层增强)
传递逻辑 可选传递(处理不了才传) 必须传递(增强后调用目标)
处理器关系 平等的处理者 层层包裹的增强者
典型场景 校验、过滤、审批 功能增强(日志、缓存)

总结

  1. 责任链模式的核心是请求沿处理器链条传递,直到被处理或链条结束,核心价值是解耦请求发送者和接收者,支持灵活扩展;

  2. 实战中常用两种构建方式:手动构建(简单场景)、Spring 自动组装(生产场景);

  3. 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)捕获拦截器中未处理的异常;
  • 统一异常返回格式,避免前端解析失败。

总结

  1. Spring MVC 拦截器链是责任链模式的经典应用,核心是通过HandlerInterceptor定义处理器,WebMvcConfigurer注册并指定执行顺序;
  2. 登录拦截器 + 权限拦截器是最常见的拦截器链组合,实现 "先登录校验,后权限校验" 的流水线处理;
  3. 关键要点:preHandle返回false终止链条、ThreadLocal 清理防止内存泄漏、白名单放行无需拦截的接口。

相关推荐
mit6.8241 天前
Agent memory发展路线
算法
青桔柠薯片1 天前
Linux I/O多路复用:深入浅出poll与epoll
linux·运维·服务器·算法
哈哈很哈哈1 天前
逻辑回归Logistic Regression
算法·机器学习·逻辑回归
甄心爱学习1 天前
【极大似然估计/最大化后验】为什么逻辑回归要使用交叉熵损失函数
算法·机器学习·逻辑回归
数据中穿行1 天前
单例设计模式全方位深度解析
设计模式
郝学胜-神的一滴1 天前
深度学习入门全解析:从核心概念到实战基础 | 技术研讨会精华总结
人工智能·python·深度学习·算法·cnn
梯度下降中1 天前
CNN原理精讲
人工智能·算法·机器学习
Ivanqhz1 天前
活跃范围重写(Live Range Rewriting)
开发语言·c++·后端·算法·rust
xiaoye-duck1 天前
《算法题讲解指南:优选算法-链表》--51.两数相加,52.两两交换链表中的节点
数据结构·算法·链表
Cosolar1 天前
阿里CoPaw进阶使用手册:从新手到高手的完整指南
人工智能·后端·算法