Spring Boot拦截器与过滤器深度解析

版本兼容说明 :本文代码示例基于 Spring Boot 3.x + Spring Framework 6.x,部分概念适用于Spring Boot 2.x版本

目录

[1. 拦截器(Interceptor)深度解析](#1. 拦截器(Interceptor)深度解析)

[1.1 核心定义](#1.1 核心定义)

[1.2 实现机制详解](#1.2 实现机制详解)

[1.2.1 HandlerInterceptor接口三大方法](#1.2.1 HandlerInterceptor接口三大方法)

[1.2.2 完整的拦截器实现示例](#1.2.2 完整的拦截器实现示例)

[1.2.3 拦截器注册配置](#1.2.3 拦截器注册配置)

[1.2.4 拦截器链执行顺序详解](#1.2.4 拦截器链执行顺序详解)

[1.2.5 与Spring IoC容器的整合特性](#1.2.5 与Spring IoC容器的整合特性)

[1.3 使用场景与典型案例](#1.3 使用场景与典型案例)

[1.3.1 场景一:权限验证](#1.3.1 场景一:权限验证)

[1.3.2 场景二:性能监控](#1.3.2 场景二:性能监控)

[1.3.3 场景三:日志记录](#1.3.3 场景三:日志记录)

[2. 过滤器(Filter)深度解析](#2. 过滤器(Filter)深度解析)

[2.1 核心定义](#2.1 核心定义)

[2.2 实现机制详解](#2.2 实现机制详解)

[2.2.1 Filter接口三大方法](#2.2.1 Filter接口三大方法)

[2.2.2 完整的过滤器实现示例](#2.2.2 完整的过滤器实现示例)

[2.2.3 过滤器注册配置方式](#2.2.3 过滤器注册配置方式)

[2.2.4 过滤器链的调用流程](#2.2.4 过滤器链的调用流程)

[2.2.5 与Servlet容器的生命周期关系](#2.2.5 与Servlet容器的生命周期关系)

[2.3 使用场景与典型案例](#2.3 使用场景与典型案例)

[2.3.1 场景一:字符编码转换](#2.3.1 场景一:字符编码转换)

[2.3.2 场景二:XSS攻击防护](#2.3.2 场景二:XSS攻击防护)

[2.3.3 场景三:CORS跨域处理](#2.3.3 场景三:CORS跨域处理)

[3. 拦截器与过滤器的对比分析](#3. 拦截器与过滤器的对比分析)

[3.1 底层实现差异](#3.1 底层实现差异)

[3.2 执行时机对比](#3.2 执行时机对比)

[3.3 功能范围差异详解](#3.3 功能范围差异详解)

[3.3.1 作用对象差异](#3.3.1 作用对象差异)

[3.3.2 依赖注入支持差异](#3.3.2 依赖注入支持差异)

[3.3.3 异常处理差异](#3.3.3 异常处理差异)

[3.4 性能对比与测试数据](#3.4 性能对比与测试数据)

[3.5 适用场景选择指南](#3.5 适用场景选择指南)

使用过滤器的场景

使用拦截器的场景

选择决策树

[4. 拦截器与过滤器的整合使用案例](#4. 拦截器与过滤器的整合使用案例)

[4.1 企业级权限系统架构](#4.1 企业级权限系统架构)

[4.2 全链路追踪实现](#4.2 全链路追踪实现)

[4.2.1 TraceID过滤器](#4.2.1 TraceID过滤器)

[4.2.2 性能监控拦截器](#4.2.2 性能监控拦截器)

[4.2.3 整合配置](#4.2.3 整合配置)

[5. 高级应用与最佳实践](#5. 高级应用与最佳实践)

[5.1 异步请求处理](#5.1 异步请求处理)

[5.1.1 异步请求场景下的执行特点](#5.1.1 异步请求场景下的执行特点)

[5.1.2 线程安全问题](#5.1.2 线程安全问题)

[5.2 自定义注解驱动的拦截器](#5.2 自定义注解驱动的拦截器)

[5.2.1 定义日志注解](#5.2.1 定义日志注解)

[5.2.2 实现注解驱动的日志拦截器](#5.2.2 实现注解驱动的日志拦截器)

[5.2.3 使用示例](#5.2.3 使用示例)

[5.3 过滤器链的动态排序策略](#5.3 过滤器链的动态排序策略)

[5.3.1 使用@Order注解](#5.3.1 使用@Order注解)

[5.3.2 使用FilterRegistrationBean配置优先级(推荐)](#5.3.2 使用FilterRegistrationBean配置优先级(推荐))

[5.3.3 常用优先级常量](#5.3.3 常用优先级常量)

[5.4 性能优化建议](#5.4 性能优化建议)

[5.4.1 避免在拦截器/过滤器中执行Heavy操作](#5.4.1 避免在拦截器/过滤器中执行Heavy操作)

[5.4.2 合理设置拦截路径](#5.4.2 合理设置拦截路径)

[5.4.3 ThreadLocal的安全使用](#5.4.3 ThreadLocal的安全使用)

[6. 常见问题与解决方案](#6. 常见问题与解决方案)

[6.1 拦截器不生效的8种原因与排查步骤](#6.1 拦截器不生效的8种原因与排查步骤)

问题1:拦截器未注册

问题2:拦截路径配置错误

问题3:静态资源被拦截

问题4:拦截器实例化问题

问题5:@Component拦截器未生效

问题6:Order优先级配置错误

问题7:WebMvcConfigurer未生效

问题8:自定义DispatcherServlet导致拦截器失效

[6.2 过滤器与拦截器执行顺序错乱问题](#6.2 过滤器与拦截器执行顺序错乱问题)

[6.3 跨域配置冲突问题](#6.3 跨域配置冲突问题)

[6.4 拦截器/过滤器中依赖注入失败问题](#6.4 拦截器/过滤器中依赖注入失败问题)

[6.5 静态资源被拦截的解决方案](#6.5 静态资源被拦截的解决方案)

[7. 源码级深度分析](#7. 源码级深度分析)

[7.1 拦截器执行流程源码追踪](#7.1 拦截器执行流程源码追踪)

[7.1.1 DispatcherServlet的doDispatch方法](#7.1.1 DispatcherServlet的doDispatch方法)

[7.1.2 HandlerExecutionChain的调用过程](#7.1.2 HandlerExecutionChain的调用过程)

[7.1.3 拦截器链执行流程图](#7.1.3 拦截器链执行流程图)

[7.2 过滤器链实现原理](#7.2 过滤器链实现原理)

[7.2.1 ApplicationFilterChain的内部工作机制](#7.2.1 ApplicationFilterChain的内部工作机制)

[7.2.2 责任链模式在Servlet容器中的应用](#7.2.2 责任链模式在Servlet容器中的应用)

[7.2.3 过滤器链的关键特性](#7.2.3 过滤器链的关键特性)

总结


1. 拦截器(Interceptor)深度解析

1.1 核心定义

Spring MVC拦截器是基于AOP(面向切面编程)思想的Spring MVC核心组件,它能够在请求处理流程的特定位置进行拦截和增强处理。与Servlet过滤器不同,拦截器完全运行在Spring MVC框架内部,能够访问Spring IoC容器中的所有Bean。

执行时机与位置

拦截器在请求处理流程中处于DispatcherServlet之后Controller方法执行前后,这使得它能够:

  • 访问Handler方法信息(方法名、参数、注解)

  • 访问ModelAndView对象(视图和模型数据)

  • 利用Spring依赖注入获取业务Bean

  • 实现细粒度的权限控制

1.2 实现机制详解

1.2.1 HandlerInterceptor接口三大方法

java 复制代码
package org.springframework.web.servlet;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * Spring MVC拦截器接口
 * 提供了三个关键的回调方法,分别在请求处理的不同阶段执行
 */
public interface HandlerInterceptor {

    /** 
     * 前置处理:在Controller方法执行之前调用
     *
     * @param request  当前HTTP请求对象
     * @param response 当前HTTP响应对象
     * @param handler  被拦截的处理器(通常为HandlerMethod)
     * @return true-继续执行后续拦截器和Controller;false-中断请求处理
     * @throws Exception 可能抛出的异常
     */
    default boolean preHandle(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler) throws Exception {
        return true;
    }

    /**
     * 后置处理:在Controller方法执行之后、视图渲染之前调用
     *
     * @param request      当前HTTP请求对象
     * @param response     当前HTTP响应对象
     * @param handler      被拦截的处理器
     * @param modelAndView Controller返回的ModelAndView对象(可能为null)
     * @throws Exception 可能抛出的异常
     *
     * 注意:只有preHandle返回true的拦截器,其postHandle才会被调用
     */
    default void postHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            @Nullable ModelAndView modelAndView) throws Exception {
    }

    /** 
     * 完成后处理:在视图渲染完成后调用(无论是否发生异常)
     *
     * @param request  当前HTTP请求对象
     * @param response 当前HTTP响应对象
     * @param handler  被拦截的处理器
     * @param ex       Controller执行过程中抛出的异常(可能为null)
     * @throws Exception 可能抛出的异常
     *
     * 注意:只有preHandle返回true的拦截器,其afterCompletion才会被调用
     */
    default void afterCompletion(HttpServletRequest request,
                                 HttpServletResponse response,
                                 Object handler,
                                 @Nullable Exception ex) throws Exception {
    }
}

1.2.2 完整的拦截器实现示例

java 复制代码
package com.example.interceptor;

import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import java.lang.reflect.Method;
import java.util.Enumeration;

/**
 * 登录权限拦截器 - 完整实现示例
 *
 * 功能特性:
 * 1. 验证用户登录状态
 * 2. 记录请求日志
 * 3. 统计接口耗时
 * 4. 异常处理
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);

    // token存储在请求头中的key
    private static final String TOKEN_HEADER = "Authorization";

    // 用于存储请求开始时间的ThreadLocal key
    private static final String START_TIME_ATTR = "requestStartTime";

    /** 
     * 前置处理:权限验证、日志记录、计时开始
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        // 记录请求开始时间
        request.setAttribute(START_TIME_ATTR, System.currentTimeMillis());

        // 1. 获取请求基本信息
        String requestURI = request.getRequestURI();
        String method = request.getMethod();
        String remoteAddr = request.getRemoteAddr();

        logger.info("========== 请求开始 ==========");
        logger.info("请求URI: {} {}", method, requestURI);
        logger.info("客户端IP: {}", remoteAddr);

        // 2. 如果不是方法处理器(如静态资源请求),直接放行
        if (!(handler instanceof HandlerMethod)) {
            logger.debug("非Controller方法请求,直接放行");
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        String className = method.getDeclaringClass().getSimpleName();
        String methodName = method.getName();

        logger.info("目标方法: {}.{}", className, methodName);

        // 3. 检查是否有@NoAuth注解(自定义注解,跳过权限验证)
        if (method.isAnnotationPresent(NoAuth.class) ||
            handlerMethod.getBeanType().isAnnotationPresent(NoAuth.class)) {
            logger.info("方法标记了@NoAuth注解,跳过权限验证");
            return true;
        }

        // 4. 从请求头获取token
        String token = request.getHeader(TOKEN_HEADER);
        if (token == null || token.isEmpty()) {
            logger.warn("未携带Token,拒绝访问");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(JSON.toJSONString(Result.error(401, "未授权,请先登录")));
            return false;
        }

        // 5. 验证token有效性(这里调用UserService,展示依赖注入能力)
        // 通过Spring容器获取UserService Bean
        // UserService userService = SpringContextHolder.getBean(UserService.class);
        // User user = userService.validateToken(token);
        // if (user == null) {
        //     response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        //     response.setContentType("application/json;charset=UTF-8");
        //     response.getWriter().write(JSON.toJSONString(Result.error(401, "Token无效或已过期")));
        //     return false;
        // }

        // 6. 将用户信息存储到请求属性中,供后续使用
        // request.setAttribute("currentUser", user);

        logger.info("权限验证通过,继续处理请求");
        return true;
    }

    /**
     * 后置处理:可以在视图渲染前修改ModelAndView
     */
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {

        // 可以在这里对ModelAndView进行增强
        if (modelAndView != null) {
            // 例如:添加全局变量到所有视图
            modelAndView.addObject("systemTime", System.currentTimeMillis());
            modelAndView.addObject("version", "1.0.0");
        }

        logger.debug("postHandle执行完毕,ModelAndView: {}", modelAndView);
    }

    /** 
     * 完成后处理:资源清理、耗时统计、异常日志
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {

        // 1. 计算请求耗时
        Long startTime = (Long) request.getAttribute(START_TIME_ATTR);
        long endTime = System.currentTimeMillis();
        long costTime = endTime - (startTime != null ? startTime : 0);

        // 2. 记录请求结束信息
        logger.info("========== 请求结束 ==========");
        logger.info("请求URI: {} {}", request.getMethod(), request.getRequestURI());
        logger.info("请求耗时: {}ms", costTime);
        logger.info("响应状态: {}", response.getStatus());

        // 3. 处理异常(如果有)
        if (ex != null) {
            logger.error("请求处理发生异常", ex);
        }

        // 4. 清理ThreadLocal变量
        request.removeAttribute(START_TIME_ATTR);
    }
}

/**
 * 跳过权限验证的注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoAuth {
}

/** 
 * 统一响应结果类
 */
class Result {
    private int code;
    private String message;
    private Object data;

    public static Result error(int code, String message) {
        Result result = new Result();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

    // getters and setters
    public int getCode() { return code; }
    public void setCode(int code) { this.code = code; }
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    public Object getData() { return data; }
    public void setData(Object data) { this.data = data; }
}

1.2.3 拦截器注册配置

java 复制代码
package com.example.config;

import com.example.interceptor.LoginInterceptor;
import com.example.interceptor.LogInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Web MVC配置类 - 拦截器注册
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    /** 
     * 配置拦截器链
     *
     * 执行顺序:
     * - 按照注册顺序执行preHandle(先注册的先执行)
     * - 按照注册逆序执行postHandle和afterCompletion(后注册的先执行)
     *
     * 例如:注册顺序为 LoginInterceptor -> LogInterceptor
     * 则执行顺序为:
     * - preHandle: LoginInterceptor -> LogInterceptor
     * - postHandle: LogInterceptor -> LoginInterceptor
     * - afterCompletion: LogInterceptor -> LoginInterceptor
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 方式1:通过构造器创建拦截器实例(适用于无依赖的拦截器)
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/api/ **")               // 拦截路径
                .excludePathPatterns("/api/login",       // 排除路径
                                   "/api/register",
                                   "/api/public/** ",
                                   "/swagger-resources/ **",
                                   "/v3/api-docs/** ",
                                   "/doc.html")
                .order(1);                               // 拦截器执行优先级,数字越小优先级越高

        // 方式2:注入拦截器(适用于需要依赖注入的拦截器)
        // LogInterceptor logInterceptor = SpringContextHolder.getBean(LogInterceptor.class);
        // registry.addInterceptor(logInterceptor)
        //         .addPathPatterns("/ **")
        //         .excludePathPatterns("/static/** ", "/favicon.ico")
        //         .order(2);

        // 方式3:使用Bean注入方式(推荐)
        // @Bean
        // public LoginInterceptor loginInterceptor() {
        //     return new LoginInterceptor();
        // }
        //
        // @Override
        // public void addInterceptors(InterceptorRegistry registry) {
        //     registry.addInterceptor(loginInterceptor())
        //             .addPathPatterns("/api/ **")
        //             .order(1);
        // }
    }
}

1.2.4 拦截器链执行顺序详解

假设配置了三个拦截器,注册顺序为:A -> B -> C,则执行流程如下:

阶段 执行顺序 说明
preHandle A -> B -> C 按注册顺序依次执行
Controller Controller执行 目标方法处理
postHandle C -> B -> A 按注册逆序执行
afterCompletion C -> B -> A 按注册逆序执行

**关键规则 **:

  1. 如果某个拦截器的preHandle返回false,则:

    • 该拦截器后续的postHandleafterCompletion不会执行

    • 该拦截器之后的所有拦截器的preHandle也不会执行

    • 已经执行的preHandle的拦截器,其afterCompletion仍然会按逆序执行

  2. 如果Controller执行过程中抛出异常:

    • postHandle不会执行

    • afterCompletion仍然会执行,且异常对象会传入

1.2.5 与Spring IoC容器的整合特性

拦截器的最大优势在于它与Spring IoC容器的深度整合:

java 复制代码
/** 
 * 拦截器中可以使用Spring的依赖注入
 */
@Component
public class BusinessInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;        // 自动注入UserService

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;  // 自动注入RedisTemplate

    @Autowired
    private Environment environment;        // 自动注入环境配置

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        // 1. 调用业务服务
        User user = userService.getCurrentUser();

        // 2. 使用缓存
        String cacheKey = "user:info:" + user.getId();
        UserInfo cachedInfo = (UserInfo) redisTemplate.opsForValue().get(cacheKey);

        // 3. 读取配置
        String appVersion = environment.getProperty("app.version", "1.0.0");

        return true;
    }
}

1.3 使用场景与典型案例

1.3.1 场景一:权限验证

java 复制代码
/**
 * 基于注解的权限验证拦截器
 */
@Component
public class PermissionInterceptor implements HandlerInterceptor {

    @Autowired
    private PermissionService permissionService;

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        // 检查是否有@RequirePermission注解
        if (!method.isAnnotationPresent(RequirePermission.class)) {
            return true; // 没有注解,放行
        }

        RequirePermission permission = method.getAnnotation(RequirePermission.class);
        String[] requiredPerms = permission.value();

        // 获取当前用户
        User user = getCurrentUser(request);
        if (user == null) {
            response.setStatus(401);
            response.getWriter().write("未登录");
            return false;
        }

        // 验证权限
        for (String perm : requiredPerms) {
            if (!permissionService.hasPermission(user.getId(), perm)) {
                response.setStatus(403);
                response.getWriter().write("无权限:" + perm);
                return false;
            }
        }

        return true;
    }
}

1.3.2 场景二:性能监控

java 复制代码
/** 
 * 性能监控拦截器 - 统计接口调用次数、耗时、成功率
 */
@Component
public class PerformanceMonitorInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorInterceptor.class);
    private static final String START_TIME = "startTime";

    @Autowired
    private MetricsService metricsService;

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        request.setAttribute(START_TIME, System.currentTimeMillis());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {

        Long startTime = (Long) request.getAttribute(START_TIME);
        long duration = System.currentTimeMillis() - startTime;

        String uri = request.getRequestURI();
        int status = response.getStatus();
        boolean success = status >= 200 && status < 300;

        // 记录监控指标
        metricsService.recordApiCall(uri, duration, success, ex != null);

        // 记录慢查询
        if (duration > 1000) {
            logger.warn("慢接口警告: {} 耗时: {}ms", uri, duration);
        }
    }
}

1.3.3 场景三:日志记录

java 复制代码
/**
 * 请求日志拦截器 - 记录请求参数、响应结果
 */
@Component
public class RequestLogInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(RequestLogInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        // 记录请求参数
        if (request instanceof ContentCachingRequestWrapper) {
            ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
            logger.info("请求参数: {}", new String(wrapper.getContentAsByteArray(),
                                  StandardCharsets.UTF_8));
        }

        return true;
    }
}

2. 过滤器(Filter)深度解析

2.1 核心定义

Servlet过滤器是基于Servlet规范(Java EE)的组件,运行在Tomcat等Servlet容器层面,是Java Web应用的核心功能之一。过滤器在请求到达Spring MVC框架之前就已经开始工作,能够对所有进入Web应用的请求和响应进行拦截和处理。

**执行时机与位置 **:

过滤器的执行位置比拦截器更**靠前 **,在请求进入Servlet容器之后、DispatcherServlet处理之前。

2.2 实现机制详解

2.2.1 Filter接口三大方法

java 复制代码
package jakarta.servlet;

import java.io.IOException;

/** 
 * Servlet过滤器接口 - Servlet 4.0/5.0规范
 */
public interface Filter {

    /**
     * 初始化方法:在过滤器被Web容器(如Tomcat)加载时调用一次
     *
     * @param filterConfig 过滤器配置对象,包含初始化参数、ServletContext等
     * @throws ServletException 初始化异常
     *
     * 注意:该方法在整个过滤器生命周期中只执行一次
     */
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    /** 
     * 核心过滤方法:对每个请求都会调用
     *
     * @param request  ServletRequest对象(可强转为HttpServletRequest)
     * @param response ServletResponse对象(可强转为HttpServletResponse)
     * @param chain    过滤器链对象,用于调用下一个过滤器或目标资源
     * @throws IOException      IO异常
     * @throws ServletException Servlet异常
     *
     * 关键点:
     * 1. 必须调用chain.doFilter()将请求传递给下一个过滤器或目标资源
     * 2. 不调用chain.doFilter()则会中断请求链
     * 3. 在chain.doFilter()之前的代码在请求阶段执行
     * 4. 在chain.doFilter()之后的代码在响应阶段执行
     */
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException;

    /**
     * 销毁方法:在过滤器被Web容器移除时调用一次
     *
     * 注意:
     * 1. 该方法在整个过滤器生命周期中只执行一次
     * 2. 用于释放资源(关闭连接、清理缓存等)
     */
    default void destroy() {
    }
}

2.2.2 完整的过滤器实现示例

java 复制代码
package com.example.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;

import java.io.IOException;

/** 
 * 字符编码过滤器 - 完整实现示例
 *
 * 功能特性:
 * 1. 设置请求和响应的字符编码为UTF-8
 * 2. 记录请求和响应的时间
 * 3. 支持跨域配置
 */
@WebFilter(urlPatterns = "/*", filterName = "EncodingFilter")
@Order(1) // Spring Boot中使用@Order注解设置优先级(需使用FilterRegistrationBean)
public class EncodingFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(EncodingFilter.class);

    // 默认字符编码
    private static final String DEFAULT_ENCODING = "UTF-8";

    // 初始化参数:字符编码
    private String encoding;

    /**
     * 初始化方法:读取配置参数
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 从web.xml或注解中读取初始化参数
        this.encoding = filterConfig.getInitParameter("encoding");
        if (this.encoding == null || this.encoding.isEmpty()) {
            this.encoding = DEFAULT_ENCODING;
        }

        logger.info("======== EncodingFilter 初始化 ========");
        logger.info("字符编码设置为: {}", this.encoding);
    }

    /** 
     * 核心过滤方法
     */
    @Override
    public void doFilter(ServletRequest request,
                        ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        long startTime = System.currentTimeMillis();

        try {
            // ====== 请求处理 ======

            // 1. 设置字符编码
            // 必须在获取请求参数之前设置,否则可能乱码
            if (!"GET".equals(httpRequest.getMethod())) {
                // POST请求需要设置请求体编码
                request.setCharacterEncoding(encoding);
            }
            response.setCharacterEncoding(encoding);
            response.setContentType("application/json;charset=" + encoding);

            // 2. 记录请求信息
            String requestURI = httpRequest.getRequestURI();
            String method = httpRequest.getMethod();
            String clientIp = getClientIp(httpRequest);

            logger.info("【Filter请求】{} {} - IP: {}", method, requestURI, clientIp);

            // 3. 包装请求和响应(如果需要多次读取)
            // ContentCachingRequestWrapper wrappedRequest =
            //     new ContentCachingRequestWrapper(httpRequest);
            // ContentCachingResponseWrapper wrappedResponse =
            //     new ContentCachingResponseWrapper(httpResponse);

            // 4. 将请求传递给下一个过滤器或目标资源
            chain.doFilter(request, response);

            // ====== 响应处理 ======

            // 5. 记录响应信息
            long endTime = System.currentTimeMillis();
            long costTime = endTime - startTime;
            int status = httpResponse.getStatus();

            logger.info("【Filter响应】{} {} - 状态: {} - 耗时: {}ms",
                       method, requestURI, status, costTime);

        } catch (Exception e) {
            logger.error("过滤器执行异常", e);
            throw e;
        }
    }

    /**
     * 销毁方法:释放资源
     */
    @Override
    public void destroy() {
        logger.info("======== EncodingFilter 销毁 ========");
        // 释放资源的代码
    }

    /** 
     * 获取客户端真实IP(考虑代理)
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 如果是多个代理,取第一个
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

2.2.3 过滤器注册配置方式

方式一:使用@WebFilter注解(不推荐)

java 复制代码
package com.example.filter;

import jakarta.servlet.annotation.WebFilter;

/**
 * 使用注解配置
 * 注意:需要在启动类上添加@ServletComponentScan注解
 */
@WebFilter(
    urlPatterns = "/*",                    // 拦截路径
    filterName = "EncodingFilter",       // 过滤器名称
    initParams = {                       // 初始化参数
        @WebInitParam(name = "encoding", value = "UTF-8")
    },
    dispatcherTypes = {
        DispatcherType.REQUEST,          // 拦截直接请求
        DispatcherType.FORWARD,          // 拦截转发请求
        DispatcherType.INCLUDE           // 拦截包含请求
    }
)
public class EncodingFilter implements Filter {
    // 实现代码...
}

**启动类配置 **:

java 复制代码
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan // 扫描@WebFilter、@WebServlet、@WebListener注解
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

方式二:使用FilterRegistrationBean(推荐)

java 复制代码
package com.example.config;

import com.example.filter.EncodingFilter;
import com.example.filter.CorsFilter;
import com.example.filter.XssFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/** 
 * 过滤器配置类
 */
@Configuration
public class FilterConfig {

    /**
     * 注册字符编码过滤器
     * 优先级最高,确保第一个执行
     */
    @Bean
    public FilterRegistrationBean<EncodingFilter> encodingFilter() {
        FilterRegistrationBean<EncodingFilter> registration = new FilterRegistrationBean<>();

        registration.setFilter(new EncodingFilter());
        registration.setName("EncodingFilter");
        registration.addUrlPatterns("/*");           // 拦截所有请求

        // 设置初始化参数
        Map<String, String> initParams = new HashMap<>();
        initParams.put("encoding", "UTF-8");
        registration.setInitParameters(initParams);

        // 设置执行优先级(数值越小优先级越高)
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registration;
    }

    /** 
     * 注册CORS过滤器
     * 第二个执行
     */
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();

        registration.setFilter(new CorsFilter());
        registration.setName("CorsFilter");
        registration.addUrlPatterns("/*");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);

        return registration;
    }

    /**
     * 注册XSS防护过滤器
     * 第三个执行
     */
    @Bean
    public FilterRegistrationBean<XssFilter> xssFilter() {
        FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();

        registration.setFilter(new XssFilter());
        registration.setName("XssFilter");
        registration.addUrlPatterns("/*");

        // 排除某些路径
        registration.addInitParameter("exclusions", "/api/upload");

        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 2);

        return registration;
    }
}

2.2.4 过滤器链的调用流程

过滤器链采用**责任链模式 **,每个过滤器依次处理请求,并将请求传递给链中的下一个过滤器:

2.2.5 与Servlet容器的生命周期关系

2.3 使用场景与典型案例

2.3.1 场景一:字符编码转换

java 复制代码
/** 
 * 字符编码过滤器 - 统一设置UTF-8编码
 */
@WebFilter(urlPatterns = "/*")
public class CharacterEncodingFilter implements Filter {

    private String encoding = "UTF-8";

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);
        chain.doFilter(request, response);
    }
}

2.3.2 场景二:XSS攻击防护

java 复制代码
/**
 * XSS防护过滤器 - 清理用户输入中的恶意脚本
 */
@WebFilter(urlPatterns = "/*")
public class XssFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        // 包装请求,清洗参数中的XSS字符
        XssHttpServletRequestWrapper wrappedRequest =
            new XssHttpServletRequestWrapper((HttpServletRequest) request);

        chain.doFilter(wrappedRequest, response);
    }
}

/** 
 * XSS请求包装器
 */
class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        return cleanXss(value);
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) {
            return null;
        }
        String[] cleanValues = new String[values.length];
        for (int i = 0; i < values.length; i++) {
            cleanValues[i] = cleanXss(values[i]);
        }
        return cleanValues;
    }

    private String cleanXss(String value) {
        if (value == null) {
            return null;
        }
        // 简单的XSS过滤逻辑
        return value.replaceAll("<", "&lt;")
                   .replaceAll(">", "&gt;")
                   .replaceAll("\"", "&quot;")
                   .replaceAll("'", "&#x27;");
    }
}

2.3.3 场景三:CORS跨域处理

java 复制代码
/**
 * CORS跨域过滤器
 */
@WebFilter(urlPatterns = "/*")
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // 允许的域名(生产环境应该从配置读取)
        String allowedOrigins = "http://localhost:3000,https://example.com";
        String origin = httpRequest.getHeader("Origin");

        if (allowedOrigins.contains(origin)) {
            httpResponse.setHeader("Access-Control-Allow-Origin", origin);
        }

        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Max-Age", "3600");

        // 预检请求直接返回
        if ("OPTIONS".equals(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        chain.doFilter(request, response);
    }
}

3. 拦截器与过滤器的对比分析

3.1 底层实现差异

对比维度 拦截器(Interceptor) 过滤器(Filter)
所属规范 Spring MVC框架 Servlet规范(Java EE)
运行环境 Spring容器内 Servlet容器(Tomcat等)
底层实现 基于AOP动态代理 基于Servlet API
执行位置 DispatcherServlet之后 DispatcherServlet之前
依赖注入 完全支持,可注入Spring Bean 不直接支持,需通过Spring代理

3.2 执行时机对比

3.3 功能范围差异详解

3.3.1 作用对象差异

**过滤器 **:

  • 基于URL模式匹配

  • 拦截规则:/api/*/user/** *.do

  • 无法区分具体的方法或注解

java 复制代码
// 过滤器只能基于URL匹配
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
    // 无法区分 GET /user/info 和 POST /user/info
    // 无法识别 @GetMapping 或 @PostMapping 注解
}

拦截器

  • 基于Controller方法匹配

  • 可以访问HandlerMethod对象

  • 可以识别方法注解、参数类型等

java 复制代码
// 拦截器可以精确到方法级别
@Override
public boolean preHandle(HttpServletRequest request,
                        HttpServletResponse response,
                        Object handler) throws Exception {

    if (handler instanceof HandlerMethod) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        // 可以识别方法级别的信息
        if (method.isAnnotationPresent(NoAuth.class)) {
            // 跳过带@NoAuth注解的方法
            return true;
        }

        // 可以区分不同的请求方法
        GetMapping getMapping = method.getAnnotation(GetMapping.class);
        if (getMapping != null) {
            // 处理GET请求
        }
    }

    return true;
}

3.3.2 依赖注入支持差异

拦截器:原生支持Spring依赖注入

java 复制代码
@Component
public class BusinessInterceptor implements HandlerInterceptor {

    @Autowired  // ✅ 完全支持依赖注入
    private UserService userService;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private DataSource dataSource;

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        // 可以直接使用注入的Bean
        User user = userService.getCurrentUser();
        redisTemplate.opsForValue().set("key", "value");

        return true;
    }
}

过滤器:需要特殊处理才能使用依赖注入

方法一:使用@WebFilter注解(无法直接注入)

java 复制代码
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {

    // ❌ 无法直接@Autowired
    // @Autowired
    // private UserService userService;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        // ❌ userService为null
        // userService.doSomething();

        chain.doFilter(request, response);
    }
}

方法二:通过FilterRegistrationBean注入(推荐)

java 复制代码
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter(UserService userService) {
        // ✅ 可以在构造方法中注入依赖
        MyFilter filter = new MyFilter(userService);

        FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(filter);
        registration.addUrlPatterns("/*");
        registration.setOrder(1);

        return registration;
    }
}

// 过滤器类
public class MyFilter implements Filter {

    private final UserService userService;

    // ✅ 通过构造方法注入
    public MyFilter(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        // ✅ 可以使用注入的Bean
        userService.doSomething();

        chain.doFilter(request, response);
    }
}

方法三:使用@Componet + @Order + ServletContext(不推荐)

java 复制代码
@Component
@Order(1)
public class MyFilter implements Filter {

    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        // ✅ 这样也能使用
        userService.doSomething();

        chain.doFilter(request, response);
    }
}

3.3.3 异常处理差异

过滤器异常处理

java 复制代码
@WebFilter(urlPatterns = "/*")
public class ExceptionFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        try {
            chain.doFilter(request, response);

        } catch (Exception e) {
            // 捕获后续过滤器或Servlet的异常
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(500);
            httpResponse.setContentType("application/json;charset=UTF-8");
            httpResponse.getWriter().write("{\"code\":500,\"message\":\"服务器内部错误\"}");

            // 记录日志
            logger.error("过滤器捕获异常", e);
        }
    }
}

拦截器异常处理

java 复制代码
@Component
public class ExceptionInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        try {
            // 业务逻辑
            return true;

        } catch (Exception e) {
            // preHandle中的异常会中断请求链
            response.setStatus(500);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":500,\"message\":\"请求处理失败\"}");
            return false; // 不继续执行
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {

        // ✅ 即使Controller抛出异常,afterCompletion仍然会执行
        // ex参数包含了Controller抛出的异常
        if (ex != null) {
            logger.error("Controller执行异常", ex);
            // 可以在这里进行异常统计、告警等
        }
    }
}

关键差异

  • 过滤器可以捕获整个Servlet链中的异常

  • 拦截器的afterCompletion可以获取Controller抛出的异常对象

  • 拦截器的preHandle返回false或抛异常,不会触发postHandle

3.4 性能对比与测试数据

测试场景 过滤器性能 拦截器性能 说明
简单放行(无逻辑) ~0.1ms ~0.05ms 拦截器稍快,因为层级更少
字符编码设置 ~0.2ms 不适用 过滤器更适合
权限验证(含DB查询) ~5ms ~4ms 拦截器可利用连接池,略快
日志记录 ~1ms ~0.8ms 差异不大
静态资源拦截 ~0.5ms 不适用 过滤器可以拦截静态资源

性能优化建议

  1. 尽量减少拦截器/过滤器数量

  2. 避免在preHandle中执行耗时操作(如网络请求)

  3. 使用异步处理(需注意线程安全)

  4. 合理设置拦截路径,减少不必要的拦截

3.5 适用场景选择指南

使用过滤器的场景

推荐使用过滤器

  1. 字符编码统一处理

    java 复制代码
    // 必须在请求处理的最早期设置
    request.setCharacterEncoding("UTF-8");
  2. CORS跨域处理

    java 复制代码
    // 需要在预检请求阶段就响应
    response.setHeader("Access-Control-Allow-Origin", "*");
  3. XSS攻击防护

    java 复制代码
    // 需要包装请求对象,清洗所有输入
    chain.doFilter(new XssRequestWrapper(request), response);
  4. 请求/响应日志记录

    java 复制代码
    // 记录所有进入应用的请求
    logger.info("{} {}", request.getMethod(), request.getRequestURI());
  5. 静态资源处理

    java 复制代码
    // 拦截静态资源请求,添加缓存头
    response.setHeader("Cache-Control", "max-age=3600");
  6. 请求/响应压缩

    java 复制代码
    // 包装响应对象,实现GZIP压缩
    chain.doFilter(request, new GzipResponseWrapper(response));

使用拦截器的场景

推荐使用拦截器

  1. 权限验证(细粒度)

    java 复制代码
    // 可以识别方法级别的@RequirePermission注解
    if (method.isAnnotationPresent(RequirePermission.class)) {
        // 进行权限检查
    }
  2. 登录状态检查

    java 复制代码
    // 检查用户是否登录
    User user = getCurrentUser(request);
    if (user == null) {
        response.setStatus(401);
        return false;
    }
  3. 接口性能监控

    java 复制代码
    // 统计Controller方法的执行时间
    long startTime = System.currentTimeMillis();
    chain.doFilter(request, response);
    long costTime = System.currentTimeMillis() - startTime;
  4. 参数预处理/验证

    java 复制代码
    // 可以访问Controller方法的参数
    MethodParameter[] parameters = handlerMethod.getMethodParameters();
  5. ModelAndView增强

    java 复制代码
    // 在postHandle中向ModelAndView添加全局数据
    modelAndView.addObject("currentUser", getCurrentUser());
  6. 异常统一处理

    java 复制代码
    // 在afterCompletion中处理异常
    if (ex != null) {
        logger.error("业务异常", ex);
    }

选择决策树


4. 拦截器与过滤器的整合使用案例

4.1 企业级权限系统架构

一个完整的企业级权限系统通常需要多个过滤器和拦截器协同工作:

4.2 全链路追踪实现

4.2.1 TraceID过滤器

java 复制代码
package com.example.filter;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.MDC;

import java.io.IOException;
import java.util.UUID;

/**
 * TraceID生成过滤器
 *
 * 功能:
 * 1. 为每个请求生成唯一的TraceID
 * 2. 将TraceID设置到MDC中,供日志使用
 * 3. 将TraceID添加到响应头中,供前端追踪
 */
public class TraceIdFilter implements Filter {

    private static final String TRACE_ID_KEY = "traceId";
    private static final String TRACE_ID_HEADER = "X-Trace-Id";

    @Override
    public void doFilter(ServletRequest request,
                        ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        try {
            // 1. 从请求头获取TraceID(如果是微服务调用)
            String traceId = httpRequest.getHeader(TRACE_ID_HEADER);

            // 2. 如果没有TraceID,生成新的
            if (traceId == null || traceId.isEmpty()) {
                traceId = generateTraceId();
            }

            // 3. 将TraceID放入MDC(Mapped Diagnostic Context)
            MDC.put(TRACE_ID_KEY, traceId);

            // 4. 将TraceID添加到响应头
            httpResponse.setHeader(TRACE_ID_HEADER, traceId);

            // 5. 继续执行后续过滤器
            chain.doFilter(request, response);

        } finally {
            // 6. 清理MDC,避免内存泄漏
            MDC.remove(TRACE_ID_KEY);
        }
    }

    /** 
     * 生成TraceID
     * 格式:时间戳-随机数
     */
    private String generateTraceId() {
        return System.currentTimeMillis() + "-" +
               UUID.randomUUID().toString().replace("-", "").substring(0, 8);
    }
}

4.2.2 性能监控拦截器

java 复制代码
package com.example.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 性能监控拦截器
 *
 * 功能:
 * 1. 记录每个接口的执行时间
 * 2. 输出结构化的访问日志
 * 3. 慢接口告警
 */
@Component
public class PerformanceMonitorInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorInterceptor.class);
    private static final Logger accessLogger = LoggerFactory.getLogger("ACCESS_LOG");
    private static final String START_TIME_ATTR = "startTime";
    private static final String TRACE_ID_KEY = "traceId";

    // 慢接口阈值(毫秒)
    private static final long SLOW_THRESHOLD = 1000;

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        // 记录请求开始时间
        request.setAttribute(START_TIME_ATTR, System.currentTimeMillis());

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {

        Long startTime = (Long) request.getAttribute(START_TIME_ATTR);
        long endTime = System.currentTimeMillis();
        long costTime = endTime - (startTime != null ? startTime : 0);

        // 构建访问日志
        AccessLog log = new AccessLog();
        log.setTraceId(MDC.get(TRACE_ID_KEY));
        log.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
        log.setMethod(request.getMethod());
        log.setUri(request.getRequestURI());
        log.setQueryString(request.getQueryString());
        log.setClientIp(getClientIp(request));
        log.setUserAgent(request.getHeader("User-Agent"));
        log.setStatus(response.getStatus());
        log.setCostTime(costTime);
        log.setException(ex != null ? ex.getClass().getSimpleName() : null);

        // 输出访问日志
        accessLogger.info(JSON.toJSONString(log));

        // 慢接口告警
        if (costTime > SLOW_THRESHOLD) {
            logger.warn("[慢接口告警] URI: {}, Cost: {}ms, Status: {}",
                       log.getUri(), costTime, log.getStatus());
        }
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /** 
     * 访问日志对象
     */
    static class AccessLog {
        private String traceId;
        private String timestamp;
        private String method;
        private String uri;
        private String queryString;
        private String clientIp;
        private String userAgent;
        private int status;
        private long costTime;
        private String exception;

        // getters and setters
        public String getTraceId() { return traceId; }
        public void setTraceId(String traceId) { this.traceId = traceId; }
        public String getTimestamp() { return timestamp; }
        public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
        public String getMethod() { return method; }
        public void setMethod(String method) { this.method = method; }
        public String getUri() { return uri; }
        public void setUri(String uri) { this.uri = uri; }
        public String getQueryString() { return queryString; }
        public void setQueryString(String queryString) { this.queryString = queryString; }
        public String getClientIp() { return clientIp; }
        public void setClientIp(String clientIp) { this.clientIp = clientIp; }
        public String getUserAgent() { return userAgent; }
        public void setUserAgent(String userAgent) { this.userAgent = userAgent; }
        public int getStatus() { return status; }
        public void setStatus(int status) { this.status = status; }
        public long getCostTime() { return costTime; }
        public void setCostTime(long costTime) { this.costTime = costTime; }
        public String getException() { return exception; }
        public void setException(String exception) { this.exception = exception; }
    }
}

4.2.3 整合配置

java 复制代码
package com.example.config;

import com.example.filter.TraceIdFilter;
import com.example.filter.CorsFilter;
import com.example.filter.XssFilter;
import com.example.interceptor.PerformanceMonitorInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Web配置类 - 整合过滤器与拦截器
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private PerformanceMonitorInterceptor performanceMonitorInterceptor;

    // ==================== 过滤器配置 ====================

    /** 
     * CORS过滤器 - 最高优先级
     */
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new CorsFilter());
        registration.setName("CorsFilter");
        registration.addUrlPatterns("/*");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }

    /**
     * XSS防护过滤器 - 第二优先级
     */
    @Bean
    public FilterRegistrationBean<XssFilter> xssFilter() {
        FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new XssFilter());
        registration.setName("XssFilter");
        registration.addUrlPatterns("/*");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
        return registration;
    }

    /** 
     * TraceID过滤器 - 第三优先级
     */
    @Bean
    public FilterRegistrationBean<TraceIdFilter> traceIdFilter() {
        FilterRegistrationBean<TraceIdFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new TraceIdFilter());
        registration.setName("TraceIdFilter");
        registration.addUrlPatterns("/*");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 2);
        return registration;
    }

    // ==================== 拦截器配置 ====================

    /**
     * 配置拦截器链
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 性能监控拦截器 - 第一个执行
        registry.addInterceptor(performanceMonitorInterceptor)
                .addPathPatterns("/api/** ")
                .excludePathPatterns("/api/health", "/api/metrics")
                .order(1);

        // 其他拦截器...
    }
}

5. 高级应用与最佳实践

5.1 异步请求处理

5.1.1 异步请求场景下的执行特点

Spring MVC支持异步请求处理(DeferredResultCallableWebAsyncTask),在异步场景下,拦截器的执行时机会有所不同:

java 复制代码
@RestController
@RequestMapping("/api")
public class AsyncController {

    /**
     * 异步请求示例
     */
    @GetMapping("/async")
    public Callable<Result> asyncRequest() {

        // preHandle执行时机:在返回Callable之前
        // postHandle执行时机:在Callable执行完成、视图渲染之前
        // afterCompletion执行时机:在视图渲染完成之后

        return () -> {
            // 在新线程中执行的业务逻辑
            Thread.sleep(2000);
            return Result.success("异步处理完成");
        };
    }
}

**执行流程 **:

5.1.2 线程安全问题

在异步场景下,拦截器可能在不同的线程中执行,需要注意ThreadLocal的使用:

java 复制代码
/** 
 * 线程安全的拦截器示例
 */
@Component
public class AsyncSafeInterceptor implements HandlerInterceptor {

    // ❌ 不使用ThreadLocal存储请求数据(异步场景可能丢失)
    // private static final ThreadLocal<User> currentUser = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        // ❌ 错误做法:存储到ThreadLocal
        // currentUser.set(getUser(request));

        // ✅ 正确做法:存储到请求属性中
        request.setAttribute("currentUser", getUser(request));

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {

        // ✅ 正确做法:从请求属性中获取
        User user = (User) request.getAttribute("currentUser");

        // ❌ 如果使用了ThreadLocal,这里需要清理
        // currentUser.remove();
    }

    private User getUser(HttpServletRequest request) {
        // 从token解析用户
        return new User();
    }
}

5.2 自定义注解驱动的拦截器

5.2.1 定义日志注解

java 复制代码
package com.example.annotation;

import java.lang.annotation.*;

/**
 * 接口日志注解
 *
 * 使用示例:
 * @LogRequired(module = "用户管理", action = "查询用户")
 * @GetMapping("/user/{id}")
 * public Result getUser(@PathVariable Long id) { ... }
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogRequired {

    /** 
     * 模块名称
     */
    String module() default "";

    /**
     * 操作类型
     */
    String action() default "";

    /** 
     * 操作描述
     */
    String description() default "";

    /**
     * 是否记录参数
     */
    boolean logParams() default true;

    /** 
     * 是否记录返回值
     */
    boolean logResult() default false;
}

5.2.2 实现注解驱动的日志拦截器

java 复制代码
package com.example.interceptor;

import com.alibaba.fastjson2.JSON;
import com.example.annotation.LogRequired;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.lang.reflect.Method;

/**
 * 注解驱动的日志拦截器
 *
 * 功能:
 * 1. 检查方法是否有@LogRequired注解
 * 2. 记录接口访问日志到数据库
 * 3. 支持异步日志记录
 */
@Component
public class LogInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

    // 使用ThreadLocal存储日志上下文
    private static final ThreadLocal<OperationLog> LOG_CONTEXT = new NamedThreadLocal<>("Log-Context");

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        // 检查是否有@LogRequired注解
        if (!method.isAnnotationPresent(LogRequired.class)) {
            return true;
        }

        LogRequired logAnnotation = method.getAnnotation(LogRequired.class);

        // 构建日志对象
        OperationLog operationLog = new OperationLog();
        operationLog.setModule(logAnnotation.module());
        operationLog.setAction(logAnnotation.action());
        operationLog.setDescription(logAnnotation.description());
        operationLog.setUri(request.getRequestURI());
        operationLog.setMethod(request.getMethod());
        operationLog.setIp(getClientIp(request));
        operationLog.setStartTime(System.currentTimeMillis());

        // 记录请求参数
        if (logAnnotation.logParams()) {
            operationLog.setParams(getRequestParams(request));
        }

        // 存储到ThreadLocal
        LOG_CONTEXT.set(operationLog);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {

        try {
            OperationLog operationLog = LOG_CONTEXT.get();
            if (operationLog == null) {
                return;
            }

            // 设置结束时间和耗时
            operationLog.setEndTime(System.currentTimeMillis());
            operationLog.setCostTime(operationLog.getEndTime() - operationLog.getStartTime());

            // 设置响应状态
            operationLog.setStatus(response.getStatus());

            // 设置异常信息
            if (ex != null) {
                operationLog.setErrorMsg(ex.getMessage());
            }

            // 异步保存日志(避免影响主流程)
            saveLogAsync(operationLog);

        } finally {
            // 清理ThreadLocal
            LOG_CONTEXT.remove();
        }
    }

    /** 
     * 异步保存日志
     */
    private void saveLogAsync(OperationLog operationLog) {
        // 使用线程池异步执行
        // logExecutor.execute(() -> logService.save(operationLog));

        // 这里简单打印
        logger.info("接口日志: {}", JSON.toJSONString(operationLog));
    }

    /**
     * 获取请求参数
     */
    private String getRequestParams(HttpServletRequest request) {
        // 简化处理,实际项目中应该使用ContentCachingRequestWrapper
        StringBuilder params = new StringBuilder();
        request.getParameterMap().forEach((key, values) -> {
            params.append(key).append("=").append(String.join(",", values)).append("&");
        });
        return params.length() > 0 ?
            params.substring(0, params.length() - 1) : "";
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /** 
     * 操作日志实体
     */
    static class OperationLog {
        private String module;
        private String action;
        private String description;
        private String uri;
        private String method;
        private String ip;
        private String params;
        private long startTime;
        private long endTime;
        private long costTime;
        private int status;
        private String errorMsg;

        // getters and setters
        public String getModule() { return module; }
        public void setModule(String module) { this.module = module; }
        public String getAction() { return action; }
        public void setAction(String action) { this.action = action; }
        public String getDescription() { return description; }
        public void setDescription(String description) { this.description = description; }
        public String getUri() { return uri; }
        public void setUri(String uri) { this.uri = uri; }
        public String getMethod() { return method; }
        public void setMethod(String method) { this.method = method; }
        public String getIp() { return ip; }
        public void setIp(String ip) { this.ip = ip; }
        public String getParams() { return params; }
        public void setParams(String params) { this.params = params; }
        public long getStartTime() { return startTime; }
        public void setStartTime(long startTime) { this.startTime = startTime; }
        public long getEndTime() { return endTime; }
        public void setEndTime(long endTime) { this.endTime = endTime; }
        public long getCostTime() { return costTime; }
        public void setCostTime(long costTime) { this.costTime = costTime; }
        public int getStatus() { return status; }
        public void setStatus(int status) { this.status = status; }
        public String getErrorMsg() { return errorMsg; }
        public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; }
    }
}

5.2.3 使用示例

java 复制代码
@RestController
@RequestMapping("/api/user")
public class UserController {

    /**
     * 使用@LogRequired注解记录日志
     */
    @LogRequired(
        module = "用户管理",
        action = "查询用户",
        description = "根据ID查询用户详情",
        logParams = true,
        logResult = false
    )
    @GetMapping("/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        User user = userService.getById(id);
        return Result.success(user);
    }

    /** 
     * 不记录日志的方法
     */
    @GetMapping("/health")
    public Result health() {
        return Result.success("OK");
    }
}

5.3 过滤器链的动态排序策略

5.3.1 使用@Order注解

java 复制代码
import org.springframework.core.annotation.Order;

/**
 * 使用@Order注解设置过滤器优先级
 *
 * 注意:
 * 1. 只有通过FilterRegistrationBean注册的过滤器才支持@Order注解
 * 2. @Order的值越小,优先级越高
 * 3. 默认优先级为Ordered.LOWEST_PRECEDENCE(最大值)
 */
@Component
@Order(1) // 数字越小,执行越早
public class FirstFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }
}

@Component
@Order(2)
public class SecondFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }
}

5.3.2 使用FilterRegistrationBean配置优先级(推荐)

java 复制代码
@Configuration
public class FilterConfig {

    /** 
     * 方式一:直接设置Order属性(推荐)
     */
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new CorsFilter());
        registration.setName("CorsFilter");
        registration.addUrlPatterns("/*");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最高优先级
        return registration;
    }

    @Bean
    public FilterRegistrationBean<EncodingFilter> encodingFilter() {
        FilterRegistrationBean<EncodingFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new EncodingFilter());
        registration.setName("EncodingFilter");
        registration.addUrlPatterns("/*");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
        return registration;
    }

    /**
     * 方式二:使用@Order注解
     */
    @Bean
    @Order(3)
    public FilterRegistrationBean<XssFilter> xssFilter() {
        FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new XssFilter());
        registration.setName("XssFilter");
        registration.addUrlPatterns("/*");
        return registration;
    }
}

5.3.3 常用优先级常量

java 复制代码
import org.springframework.core.Ordered;

/** 
 * 过滤器优先级常量
 */
public class FilterOrderConstants {

    /**
     * 最高优先级 - 最先执行
     */
    public static final int HIGHEST_PRECEDENCE = Ordered.HIGHEST_PRECEDENCE; // Integer.MIN_VALUE

    /** 
     * CORS过滤器
     */
    public static final int CORS_FILTER = Ordered.HIGHEST_PRECEDENCE + 1;

    /**
     * 字符编码过滤器
     */
    public static final int ENCODING_FILTER = Ordered.HIGHEST_PRECEDENCE + 2;

    /** 
     * XSS防护过滤器
     */
    public static final int XSS_FILTER = Ordered.HIGHEST_PRECEDENCE + 3;

    /**
     * TraceID过滤器
     */
    public static final int TRACE_ID_FILTER = Ordered.HIGHEST_PRECEDENCE + 4;

    /** 
     * 默认优先级 - 最低优先级
     */
    public static final int LOWEST_PRECEDENCE = Ordered.LOWEST_PRECEDENCE; // Integer.MAX_VALUE
}

5.4 性能优化建议

5.4.1 避免在拦截器/过滤器中执行Heavy操作

错误做法

java 复制代码
@Override
public boolean preHandle(HttpServletRequest request,
                        HttpServletResponse response,
                        Object handler) throws Exception {

    // ❌ 同步调用第三方API
    String result = HttpClient.get("http://external-api.com/check");

    // ❌ 同步查询数据库
    User user = userService.findByToken(token);

    // ❌ 复杂的计算
    for (int i = 0; i < 1000000; i++) {
        // 耗时计算
    }

    return true;
}

正确做法

java 复制代码
@Component
public class OptimizedInterceptor implements HandlerInterceptor {

    @Autowired
    private ExecutorService asyncExecutor;

    @Autowired
    private CacheManager cacheManager;

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        // ✅ 使用缓存
        String cacheKey = "user:token:" + token;
        User user = cacheManager.get(cacheKey, User.class);

        if (user == null) {
            // ✅ 异步刷新缓存
            asyncExecutor.execute(() -> {
                User dbUser = userService.findByToken(token);
                cacheManager.set(cacheKey, dbUser);
            });

            // ✅ 如果缓存未命中,可以使用默认值或快速失败
            user = getDefaultUser();
        }

        return true;
    }
}

5.4.2 合理设置拦截路径

错误做法

java 复制代码
@Override
public void addInterceptors(InterceptorRegistry registry) {

    // ❌ 拦截所有请求,包括静态资源
    registry.addInterceptor(new LogInterceptor())
            .addPathPatterns("/ **");
}

✅ **正确做法 **:

java 复制代码
@Override
public void addInterceptors(InterceptorRegistry registry) {

    registry.addInterceptor(new LogInterceptor())
            .addPathPatterns("/api/** ")           // 只拦截API接口
            .excludePathPatterns(
                "/api/health",                  // 排除健康检查接口
                "/api/metrics",                 // 排除监控接口
                "/swagger-resources/ **",        // 排除Swagger资源
                "/v3/api-docs/** ",
                "/doc.html",
                "/webjars/ **",
                "/favicon.ico"                  // 排除favicon
            );
}

5.4.3 ThreadLocal的安全使用

❌ **错误做法 **:

java 复制代码
@Component
public class UnsafeInterceptor implements HandlerInterceptor {

    private static final ThreadLocal<User> currentUser = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        currentUser.set(getUser(request));
        return true;
    }

    // ❌ 忘记清理ThreadLocal,导致内存泄漏
}

✅ **正确做法 **:

java 复制代码
@Component
public class SafeInterceptor implements HandlerInterceptor {

    private static final ThreadLocal<User> currentUser = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        currentUser.set(getUser(request));
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {

        try {
            // 业务逻辑...
        } finally {
            // ✅ 确保在finally块中清理ThreadLocal
            currentUser.remove();
        }
    }
}

6. 常见问题与解决方案

6.1 拦截器不生效的8种原因与排查步骤

问题1:拦截器未注册

**现象 **:拦截器的preHandle方法从未执行

**原因 **:忘记调用**registry.addInterceptor()**注册拦截器

**解决方案 **:

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // ✅ 确保添加了拦截器注册
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/api/** ");
    }
}

问题2:拦截路径配置错误

现象:拦截器没有拦截预期的请求

原因addPathPatternsexcludePathPatterns配置错误

排查步骤

java 复制代码
@Override
public void addInterceptors(InterceptorRegistry registry) {

    registry.addInterceptor(new MyInterceptor())
            .addPathPatterns("/api/ **")      // ✅ 确认路径匹配规则
            .excludePathPatterns(
                "/api/public/** ",           // ✅ 检查排除路径
                "/swagger-ui/ **",
                "/v3/api-docs/** "
            );
}

路径匹配规则

路径模式 说明 示例
/api/user 精确匹配 只匹配 /api/user
/api/* 单级匹配 匹配 /api/user,不匹配 /api/user/info
/api/** 多级匹配 匹配 /api/user/api/user/info
*.do 后缀匹配 匹配 /user.do,不匹配 /user

问题3:静态资源被拦截

**现象 **:访问静态资源(CSS、JS、图片)时被拦截

**原因 **:拦截器配置了 /** 路径

解决方案

java 复制代码
@Override
public void addInterceptors(InterceptorRegistry registry) {

    registry.addInterceptor(new MyInterceptor())
            .addPathPatterns("/api/ **")
            // ✅ 排除静态资源路径
            .excludePathPatterns(
                "/static/** ",
                "/css/ **",
                "/js/** ",
                "/images/ **",
                "/favicon.ico"
            );
}

问题4:拦截器实例化问题

**现象 **:拦截器中的@Autowired注入的Bean为null

**原因 **:直接new拦截器实例,而非通过Spring容器管理

**解决方案 **:

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    // ❌ 错误:直接new,无法注入依赖
    // @Override
    // public void addInterceptors(InterceptorRegistry registry) {
    //     registry.addInterceptor(new MyInterceptor());
    // }

    // ✅ 方式一:通过Bean方法注册
    @Bean
    public MyInterceptor myInterceptor() {
        return new MyInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor());
    }

    // ✅ 方式二:直接注入拦截器(如果拦截器使用了@Component)
    // @Autowired
    // private MyInterceptor myInterceptor;
    //
    // @Override
    // public void addInterceptors(InterceptorRegistry registry) {
    //     registry.addInterceptor(myInterceptor);
    // }
}

问题5:@Component拦截器未生效

**现象 **:拦截器使用了@Component注解,但仍不生效

**原因 **:没有在WebMvcConfigurer中注册拦截器

**解决方案 **:

java 复制代码
// ✅ 正确做法:使用@Component + 注册
@Component
public class MyInterceptor implements HandlerInterceptor {
    // ...
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private MyInterceptor myInterceptor;  // ✅ 注入拦截器

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor)  // ✅ 注册拦截器
                .addPathPatterns("/api/** ");
    }
}

问题6:Order优先级配置错误

现象:拦截器执行顺序不符合预期

原因order方法未调用或数值设置错误

解决方案

java 复制代码
@Override
public void addInterceptors(InterceptorRegistry registry) {

    // ✅ 设置执行优先级,数字越小越先执行
    registry.addInterceptor(new AuthInterceptor())
            .addPathPatterns("/api/ **")
            .order(1);  // 数字越小,优先级越高

    registry.addInterceptor(new LogInterceptor())
            .addPathPatterns("/api/** ")
            .order(2);  // 第二个执行
}

问题7:WebMvcConfigurer未生效

现象:WebMvcConfigurer配置类中的拦截器未生效

原因 :配置类未添加@Configuration注解,或存在多个配置类冲突

解决方案

java 复制代码
// ✅ 确保添加@Configuration注解
@Configuration
@EnableWebMvc  // 如果使用默认配置,不需要此注解
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/api/ **");
    }
}

问题8:自定义DispatcherServlet导致拦截器失效

**现象 **:自定义了DispatcherServlet,拦截器失效

**原因 **:拦截器注册到了默认的DispatcherServlet,而非自定义的DispatcherServlet

**解决方案 **:

java 复制代码
@Configuration
public class ServletConfig {

    @Bean
    public DispatcherServlet customDispatcherServlet() {
        return new DispatcherServlet();
    }

    @Bean
    public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration() {
        ServletRegistrationBean<DispatcherServlet> registration =
            new ServletRegistrationBean<>(customDispatcherServlet(), "/custom/*");
        registration.setName("customDispatcherServlet");
        return registration;
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    // ✅ 拦截器会注册到默认的DispatcherServlet
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/** ");
    }
}

6.2 过滤器与拦截器执行顺序错乱问题

问题:过滤器与拦截器的执行顺序不符合预期

原因

  1. 过滤器的order配置错误

  2. 拦截器的order配置错误

  3. 混合使用@Order注解和setOrder方法

解决方案:

java 复制代码
@Configuration
public class WebConfig {

    // ==================== 过滤器配置 ====================
    // 执行顺序:Filter1 -> Filter2 -> Filter3
    // 返回顺序:Filter3 -> Filter2 -> Filter1

    @Bean
    public FilterRegistrationBean<Filter1> filter1() {
        FilterRegistrationBean<Filter1> registration = new FilterRegistrationBean<>();
        registration.setFilter(new Filter1());
        registration.setOrder(1);  // ✅ 最先执行
        return registration;
    }

    @Bean
    public FilterRegistrationBean<Filter2> filter2() {
        FilterRegistrationBean<Filter2> registration = new FilterRegistrationBean<>();
        registration.setFilter(new Filter2());
        registration.setOrder(2);
        return registration;
    }

    @Bean
    public FilterRegistrationBean<Filter3> filter3() {
        FilterRegistrationBean<Filter3> registration = new FilterRegistrationBean<>();
        registration.setFilter(new Filter3());
        registration.setOrder(3);
        return registration;
    }
}

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    // ==================== 拦截器配置 ====================
    // 执行顺序:Interceptor1 -> Interceptor2 -> Interceptor3
    // 返回顺序:Interceptor3 -> Interceptor2 -> Interceptor1

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new Interceptor1())
                .order(1)  // ✅ 最先执行preHandle
                .addPathPatterns("/api/ **");

        registry.addInterceptor(new Interceptor2())
                .order(2)
                .addPathPatterns("/api/** ");

        registry.addInterceptor(new Interceptor3())
                .order(3)
                .addPathPatterns("/api/ **");
    }
}

6.3 跨域配置冲突问题

问题:CORS配置在过滤器与Spring注解中冲突

**原因 **:

  1. 同时配置了**@CrossOrigin**注解和CORS过滤器

  2. CORS过滤器的优先级低于其他过滤器

解决方案:

方式一:仅使用@CrossOrigin注解(简单场景)

java 复制代码
@RestController
@RequestMapping("/api")
@CrossOrigin(
    origins = "http://localhost:3000",
    allowedHeaders = "*",
    methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE},
    allowCredentials = "true"
)
public class ApiController {

    // 不需要CORS过滤器
}

方式二:仅使用CORS过滤器(推荐)

java 复制代码
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();

        registration.setFilter(new CorsFilter());
        registration.addUrlPatterns("/*");

        // ✅ CORS过滤器必须第一个执行(最高优先级)
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registration;
    }
}

// CorsFilter实现
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // 跨域配置
        String origin = httpRequest.getHeader("Origin");
        httpResponse.setHeader("Access-Control-Allow-Origin", origin);
        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Max-Age", "3600");

        // 预检请求直接返回
        if ("OPTIONS".equals(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        chain.doFilter(request, response);
    }
}

方式三:使用Spring MVC的CORS配置(推荐)

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/api/** ")  // 拦截路径
                .allowedOrigins("http://localhost:3000")  // 允许的域名
                .allowedMethods("GET", "POST", "PUT", "DELETE")  // 允许的请求方法
                .allowedHeaders("*")  // 允许的请求头
                .allowCredentials(true)  // 允许携带Cookie
                .maxAge(3600);  // 预检请求缓存时间(秒)
    }
}

6.4 拦截器/过滤器中依赖注入失败问题

问题:拦截器或过滤器中@Autowired的Bean为null

原因

  1. 过滤器直接new实例,而非通过Spring容器管理

  2. 拦截器使用@Order注解但未注册

解决方案:

过滤器依赖注入

java 复制代码
// ❌ 错误做法:无法注入依赖
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {

    @Autowired  // ❌ 为null
    private UserService userService;
}

// ✅ 正确做法:通过FilterRegistrationBean注入
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter(UserService userService) {
        // ✅ 在构造方法中注入依赖
        MyFilter filter = new MyFilter(userService);

        FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(filter);
        registration.addUrlPatterns("/*");
        registration.setOrder(1);

        return registration;
    }
}

// 过滤器类
public class MyFilter implements Filter {

    private final UserService userService;

    public MyFilter(UserService userService) {
        this.userService = userService;  // ✅ 构造注入
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {

        userService.doSomething();  // ✅ 可以使用

        chain.doFilter(request, response);
    }
}

拦截器依赖注入

java 复制代码
// ✅ 方式一:使用@Component注解
@Component
public class MyInterceptor implements HandlerInterceptor {

    @Autowired  // ✅ 可以注入
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {
        userService.doSomething();
        return true;
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private MyInterceptor myInterceptor;  // ✅ 注入拦截器

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor)  // ✅ 注册拦截器
                .addPathPatterns("/api/ **");
    }
}

// ✅ 方式二:通过@Bean方法创建
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public MyInterceptor myInterceptor(UserService userService) {
        MyInterceptor interceptor = new MyInterceptor();
        interceptor.setUserService(userService);  // Setter注入
        return interceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor())
                .addPathPatterns("/api/** ");
    }
}

6.5 静态资源被拦截的解决方案

问题:静态资源(CSS、JS、图片)被拦截器或过滤器拦截

解决方案:

方式一:配置拦截器排除静态资源路径

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/ **")
                // ✅ 排除静态资源路径
                .excludePathPatterns(
                    "/static/** ",
                    "/css/ **",
                    "/js/** ",
                    "/images/ **",
                    "/webjars/** ",
                    "/favicon.ico"
                );
    }
}

方式二:覆盖默认的静态资源配置

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * 配置静态资源处理器
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/static/** ")
                .addResourceLocations("classpath:/static/");

        registry.addResourceHandler("/css/ **")
                .addResourceLocations("classpath:/static/css/");

        registry.addResourceHandler("/js/** ")
                .addResourceLocations("classpath:/static/js/");

        registry.addResourceHandler("/images/ **")
                .addResourceLocations("classpath:/static/images/");

        // 设置缓存过期时间(秒)
        registry.addResourceHandler("/static/** ")
                .setCachePeriod(3600);
    }
}

方式三:使用Nginx代理静态资源(生产环境推荐)

TypeScript 复制代码
# Nginx配置示例
server {
    listen 80;
    server_name example.com;

    # 静态资源由Nginx直接返回
    location /static/ {
        alias /var/www/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # 动态请求转发到Spring Boot
    location /api/ {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

7. 源码级深度分析

7.1 拦截器执行流程源码追踪

7.1.1 DispatcherServlet的doDispatch方法

java 复制代码
package org.springframework.web.servlet;

/**
 * DispatcherServlet - Spring MVC的前端控制器
 * 核心请求分发方法
 */
public class DispatcherServlet extends FrameworkServlet {

    /** 
     * 分发请求到处理器
     *
     * 执行流程:
     * 1. 获取处理器执行链
     * 2. 应用拦截器的preHandle方法
     * 3. 执行处理器
     * 4. 应用拦截器的postHandle方法
     * 5. 应用拦截器的afterCompletion方法
     */
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                // 步骤1:根据请求获取处理器执行链(包含Handler和Interceptor链)
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 步骤2:获取处理器适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // 处理GET、HEAD请求的Last-Modified
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                // ========== 关键点1:应用拦截器的preHandle方法 ==========
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;  // 如果preHandle返回false,直接返回
                }

                // 步骤3:执行处理器(Controller方法)
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                // 处理默认视图名
                applyDefaultViewName(processedRequest, mv);

                // ========== 关键点2:应用拦截器的postHandle方法 ==========
                mappedHandler.applyPostHandle(processedRequest, response, mv);

            } catch (Exception ex) {
                dispatchException = ex;
            } catch (Throwable err) {
                // 处理错误
                dispatchException = new ServletException("Handler dispatch failed", err);
            }

            // 步骤4:处理视图渲染
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

        } catch (Exception ex) {
            // ========== 关键点3:应用拦截器的afterCompletion方法 ==========
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } catch (Throwable err) {
            // 处理错误
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                                   new ServletException("Handler processing failed", err));
        } finally {
            // 清理异步请求的资源
            if (asyncManager.isConcurrentHandlingStarted()) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // 清理文件上传资源
            cleanupMultipart(processedRequest);
        }
    }

    /**
     * 处理视图渲染结果
     * 这里会触发afterCompletion
     */
    private void processDispatchResult(HttpServletRequest request,
                                      HttpServletResponse response,
                                      HandlerExecutionChain mappedHandler,
                                      ModelAndView mv,
                                      Exception exception) throws Exception {

        boolean errorView = false;

        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            } else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }

        if (mv != null && !mv.wasCleared()) {
            // 渲染视图
            render(mv, request, response);
        }

        try {
            // ========== 关键点4:触发afterCompletion ==========
            if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, exception);
            }
        } catch (Exception ex) {
            // 处理异常
        }
    }
}

7.1.2 HandlerExecutionChain的调用过程

java 复制代码
package org.springframework.web.servlet;

import org.springframework.util.Assert;

/** 
 * 处理器执行链
 *
 * 包含:
 * 1. 目标处理器(Handler)
 * 2. 拦截器链
 * 3. 拦截器索引(记录执行位置)
 */
public class HandlerExecutionChain {

    private final Object handler;  // 目标处理器
    private HandlerInterceptor[] interceptors;  // 拦截器数组
    private List<HandlerInterceptor> interceptorList;  // 拦截器列表
    private int interceptorIndex = -1;  // 当前执行的拦截器索引

    /**
     * 应用拦截器的preHandle方法
     *
     * 执行顺序:从第一个拦截器开始,依次执行
     * 如果某个拦截器的preHandle返回false,则:
     * 1. 停止后续拦截器的执行
     * 2. 触发已执行拦截器的afterCompletion
     */
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {

        // 遍历拦截器链
        for (int i = 0; i < this.interceptorList.size(); i++) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);

            // 调用preHandle方法
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 如果返回false,触发afterCompletion
                triggerAfterCompletion(request, response, null);
                return false;  // 中断请求链
            }

            // 记录当前执行的拦截器索引
            this.interceptorIndex = i;
        }

        return true;  // 所有拦截器的preHandle都返回true
    }

    /** 
     * 应用拦截器的postHandle方法
     *
     * 执行顺序:从最后一个拦截器开始,逆序执行
     * 注意:只有preHandle返回true的拦截器,其postHandle才会被调用
     */
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {

        // 从最后一个拦截器开始,逆序执行
        for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }

    /**
     * 触发拦截器的afterCompletion方法
     *
     * 执行顺序:从最后一个执行preHandle的拦截器开始,逆序执行
     * 注意:
     * 1. 只有preHandle返回true的拦截器,其afterCompletion才会被调用
     * 2. 无论是否发生异常,afterCompletion都会被调用
     */
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) {

        // 从最后一个执行preHandle的拦截器开始,逆序执行
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            } catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }

    /** 
     * 添加拦截器
     */
    public void addInterceptor(HandlerInterceptor interceptor) {
        this.interceptorList.add(interceptor);
    }
}

7.1.3 拦截器链执行流程图

7.2 过滤器链实现原理

7.2.1 ApplicationFilterChain的内部工作机制

java 复制代码
package org.apache.catalina.core;

/**
 * ApplicationFilterChain - Tomcat的过滤器链实现
 *
 * 责任链模式的典型应用
 */
final class ApplicationFilterChain implements FilterChain {

    /** 
     * 过滤器数组
     */
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

    /**
     * 当前执行的过滤器索引
     */
    private int pos = 0;

    /** 
     * 过滤器总数
     */
    private int n = 0;

    /**
     * 目标Servlet(DispatcherServlet)
     */
    private Servlet servlet = null;

    /** 
     * 执行过滤器链
     *
     * 这是典型的责任链模式实现
     * 每个过滤器处理请求后,调用chain.doFilter()传递给下一个过滤器
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {

        // 检查是否还有过滤器需要执行
        if (pos < n) {
            // 获取下一个过滤器
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = filterConfig.getFilter();

            try {
                // 调用过滤器的doFilter方法
                filter.doFilter(request, response, this);

            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }

            return;
        }

        // 所有过滤器执行完毕,调用目标Servlet
        try {
            if (Globals.IS_SECURITY_ENABLED) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();

                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                // ✅ 这里调用DispatcherServlet的service方法
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        }
    }

    /**
     * 重置过滤器链
     */
    void reset() {
        this.pos = 0;
        this.n = 0;
        this.servlet = null;
    }
}

7.2.2 责任链模式在Servlet容器中的应用

**责任链模式的核心思想 **:

  1. 将请求的发送者和接收者解耦

  2. 多个处理者(过滤器)形成一条链

  3. 请求沿着链传递,直到被处理

**过滤器链的执行流程 **:

7.2.3 过滤器链的关键特性

特性1:双向处理

java 复制代码
@Override
public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {

    // ========== 请求阶段(进入)==========
    // 1. 预处理请求
    request.setCharacterEncoding("UTF-8");

    // 2. 包装请求对象
    // ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);

    // 3. 传递给下一个过滤器
    chain.doFilter(request, response);

    // ========== 响应阶段(返回)==========
    // 4. 后处理响应
    // String content = new String(wrappedResponse.getContentAsByteArray());

    // 5. 添加响应头
    // response.setHeader("X-Response-Time", String.valueOf(costTime));
}

特性2:中断请求链

java 复制代码
@Override
public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {

    // 检查某个条件
    if (!isAuthenticated(request)) {
        // 不调用chain.doFilter(),直接返回
        response.setStatus(401);
        response.getWriter().write("Unauthorized");
        return;  // ✅ 中断请求链
    }

    // 验证通过,继续执行
    chain.doFilter(request, response);
}

特性3:异常处理

java 复制代码
@Override
public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {

    try {
        chain.doFilter(request, response);

    } catch (Exception e) {
        // 捕获后续过滤器或Servlet的异常
        logger.error("过滤器捕获异常", e);

        // 统一异常响应
        response.setStatus(500);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"code\":500,\"message\":\"服务器内部错误\"}");
    }
}

总结

本文全面深入地讲解了Spring Boot中拦截器与过滤器的核心概念、实现原理及综合应用。通过对比分析,我们可以清晰地看到:

**过滤器(Filter) **:

  • 基于Servlet规范,运行在Servlet容器层面

  • 适用于字符编码、CORS、XSS防护等底层处理

  • 无法直接使用Spring依赖注入

  • 基于URL模式匹配,执行时机最早

**拦截器(Interceptor) **:

  • 基于Spring MVC框架,运行在Spring容器层面

  • 适用于权限验证、日志记录、性能监控等业务处理

  • 完全支持Spring依赖注入

  • 基于Controller方法匹配,可以识别注解

在实际项目中,通常需要**同时使用过滤器和拦截器 **,让它们各司其职,共同构建完整的请求处理链。


**参考资源 **:

相关推荐
短剑重铸之日2 小时前
《7天学会Redis》Day2 - 深入Redis数据结构与底层实现
数据结构·数据库·redis·后端
码事漫谈2 小时前
从C++到C#的转型完全指南
后端
码事漫谈2 小时前
TCP心跳机制:看不见的“生命线”
后端
亲爱的非洲野猪2 小时前
Java锁机制八股文
java·开发语言
rgeshfgreh2 小时前
C++字符串处理:STL string终极指南
java·jvm·算法
Zoey的笔记本3 小时前
「支持ISO27001的GTD协作平台」数据生命周期管理方案与加密通信协议
java·前端·数据库
lpfasd1233 小时前
Spring Boot 4.0.1 时变更清单
java·spring boot·后端
N***H4863 小时前
SpringBoot3.3.0集成Knife4j4.5.0实战
java
梦梦代码精3 小时前
《全栈开源智能体:终结企业AI拼图时代》
人工智能·后端·深度学习·小程序·前端框架·开源·语音识别