全部笔记All notes

Spring MVC 拦截器完整指南

阅读 8m 50s8m 50s read

概述

Spring MVC 拦截器(Interceptor) 是Spring框架提供的一种AOP实现,允许开发者在请求处理的不同阶段插入自定义逻辑。拦截器可以在请求到达Controller之前、Controller处理完成之后、视图渲染完成之后执行特定的业务逻辑。

核心特性

  • 非侵入性: 不需要修改现有的Controller代码
  • 链式处理: 支持多个拦截器按顺序执行
  • 灵活配置: 可以精确控制拦截的URL路径
  • 生命周期管理: 提供请求处理各个阶段的钩子方法
  • Spring集成: 与Spring容器完美整合,支持依赖注入

拦截器 vs 过滤器

特性拦截器(Interceptor)过滤器(Filter)
技术标准Spring MVC特有Servlet标准
执行时机DispatcherServlet内部DispatcherServlet之前
依赖注入✅ 支持Spring DI❌ 不支持Spring DI
访问ModelAndView✅ 可以访问❌ 无法访问
异常处理✅ 细粒度控制❌ 粗粒度控制
URL匹配✅ 支持Ant路径模式❌ 简单字符串匹配

拦截器执行流程

HTTP Request
     ↓
Filter Chain
     ↓
DispatcherServlet
     ↓
HandlerMapping
     ↓
[Interceptor.preHandle()]  ← 第一个拦截点
     ↓
HandlerAdapter
     ↓
Controller Method
     ↓
[Interceptor.postHandle()]  ← 第二个拦截点
     ↓
View Resolver
     ↓
View Rendering
     ↓
[Interceptor.afterCompletion()]  ← 第三个拦截点
     ↓
HTTP Response

💡 关键理解: 拦截器在Spring MVC的处理流程中提供了三个关键拦截点,分别用于不同的业务场景。


拦截器基础

HandlerInterceptor接口

Spring MVC提供了HandlerInterceptor接口来定义拦截器的基本行为:

package org.springframework.web.servlet;

public interface HandlerInterceptor {
    
    /**
     * 预处理回调方法,在Controller方法执行之前调用
     * @param request 当前HTTP请求
     * @param response 当前HTTP响应
     * @param handler 执行链中的下一个拦截器,通常是Controller方法
     * @return true继续执行,false中断执行
     */
    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对象
     */
    default void postHandle(HttpServletRequest request, 
                           HttpServletResponse response,
                           Object handler, 
                           ModelAndView modelAndView) throws Exception {
    }
    
    /**
     * 完成回调方法,在整个请求完成之后调用
     * @param request 当前HTTP请求
     * @param response 当前HTTP响应
     * @param handler 执行的处理器
     * @param ex 执行过程中的异常(如果有)
     */
    default void afterCompletion(HttpServletRequest request, 
                                HttpServletResponse response,
                                Object handler, 
                                Exception ex) throws Exception {
    }
}

基础拦截器实现

1. 简单日志拦截器

package com.example.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 请求日志拦截器
 * 记录每个请求的基本信息和处理时间
 */
@Component
public class LoggingInterceptor implements HandlerInterceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        
        logger.info("请求开始 - Method: {}, URI: {}, RemoteAddr: {}", 
                   request.getMethod(), 
                   request.getRequestURI(), 
                   request.getRemoteAddr());
        
        return true; // 继续执行后续拦截器和Controller
    }
    
    @Override
    public void postHandle(HttpServletRequest request, 
                          HttpServletResponse response,
                          Object handler, 
                          ModelAndView modelAndView) throws Exception {
        
        logger.info("Controller执行完成 - Status: {}", response.getStatus());
        
        if (modelAndView != null) {
            logger.info("ModelAndView - ViewName: {}, Model: {}", 
                       modelAndView.getViewName(), 
                       modelAndView.getModel().keySet());
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response,
                               Object handler, 
                               Exception ex) throws Exception {
        
        Long startTime = (Long) request.getAttribute("startTime");
        if (startTime != null) {
            long endTime = System.currentTimeMillis();
            long executeTime = endTime - startTime;
            
            logger.info("请求完成 - 执行时间: {}ms, Status: {}", executeTime, response.getStatus());
        }
        
        if (ex != null) {
            logger.error("请求处理异常", ex);
        }
    }
}

2. 用户认证拦截器

package com.example.interceptor;

import com.example.service.UserService;
import com.example.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 用户认证拦截器
 * 验证JWT Token的有效性
 */
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private UserService userService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        // 获取Authorization头
        String authHeader = request.getHeader("Authorization");
        
        if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer ")) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("{\"error\":\"Missing or invalid authorization header\"}");
            response.setContentType("application/json");
            return false;
        }
        
        // 提取Token
        String token = authHeader.substring(7);
        
        try {
            // 验证Token
            if (jwtUtil.isTokenExpired(token)) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("{\"error\":\"Token expired\"}");
                response.setContentType("application/json");
                return false;
            }
            
            // 获取用户信息
            String username = jwtUtil.getUsernameFromToken(token);
            if (!userService.isUserActive(username)) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("{\"error\":\"User account is inactive\"}");
                response.setContentType("application/json");
                return false;
            }
            
            // 将用户信息存储到请求属性中
            request.setAttribute("currentUser", username);
            request.setAttribute("token", token);
            
            return true;
            
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("{\"error\":\"Invalid token\"}");
            response.setContentType("application/json");
            return false;
        }
    }
}

拦截器配置

XML配置方式

<!-- spring-mvc.xml -->
<mvc:interceptors>
    <!-- 全局拦截器 -->
    <bean class="com.example.interceptor.LoggingInterceptor" />
    
    <!-- 路径特定拦截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/api/**" />
        <mvc:exclude-mapping path="/api/public/**" />
        <bean class="com.example.interceptor.AuthenticationInterceptor" />
    </mvc:interceptor>
    
    <!-- 多路径拦截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/admin/**" />
        <mvc:mapping path="/management/**" />
        <bean class="com.example.interceptor.AdminAuthInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

Java配置方式(推荐)

package com.example.config;

import com.example.interceptor.AuthenticationInterceptor;
import com.example.interceptor.LoggingInterceptor;
import com.example.interceptor.RateLimitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private LoggingInterceptor loggingInterceptor;
    
    @Autowired
    private AuthenticationInterceptor authenticationInterceptor;
    
    @Autowired
    private RateLimitInterceptor rateLimitInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        
        // 1. 全局日志拦截器(最低优先级)
        registry.addInterceptor(loggingInterceptor)
                .addPathPatterns("/**")
                .order(1);
        
        // 2. 限流拦截器
        registry.addInterceptor(rateLimitInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/health", "/api/status")
                .order(2);
        
        // 3. 认证拦截器(高优先级)
        registry.addInterceptor(authenticationInterceptor)
                .addPathPatterns("/api/**", "/admin/**")
                .excludePathPatterns(
                    "/api/auth/login",
                    "/api/auth/register", 
                    "/api/public/**",
                    "/admin/login"
                )
                .order(3);
    }
}

Spring Boot自动配置

package com.example.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InterceptorAutoConfiguration {
    
    /**
     * 条件化配置认证拦截器
     */
    @Bean
    @ConditionalOnProperty(name = "app.security.jwt.enabled", havingValue = "true")
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
    
    /**
     * 条件化配置限流拦截器
     */
    @Bean
    @ConditionalOnProperty(name = "app.ratelimit.enabled", havingValue = "true", matchIfMissing = true)
    public RateLimitInterceptor rateLimitInterceptor() {
        return new RateLimitInterceptor();
    }
}

实际应用场景

1. API限流拦截器

package com.example.interceptor;

import com.example.util.RedisTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * API限流拦截器
 * 基于Redis实现分布式限流
 */
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String RATE_LIMIT_KEY_PREFIX = "rate_limit:";
    private static final int DEFAULT_LIMIT = 100; // 默认每分钟100次
    private static final int TIME_WINDOW = 60; // 时间窗口:60秒
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        // 获取客户端标识(IP地址或用户ID)
        String clientId = getClientId(request);
        String key = RATE_LIMIT_KEY_PREFIX + clientId;
        
        // 获取当前请求计数
        String countStr = redisTemplate.opsForValue().get(key);
        int currentCount = countStr != null ? Integer.parseInt(countStr) : 0;
        
        if (currentCount >= DEFAULT_LIMIT) {
            // 超过限制,返回429状态码
            response.setStatus(429); // Too Many Requests
            response.setHeader("X-RateLimit-Limit", String.valueOf(DEFAULT_LIMIT));
            response.setHeader("X-RateLimit-Remaining", "0");
            response.setHeader("Retry-After", String.valueOf(TIME_WINDOW));
            response.getWriter().write("{\"error\":\"Rate limit exceeded\"}");
            response.setContentType("application/json");
            return false;
        }
        
        // 增加计数
        if (currentCount == 0) {
            // 首次请求,设置过期时间
            redisTemplate.opsForValue().set(key, "1", TIME_WINDOW, TimeUnit.SECONDS);
        } else {
            // 增加计数
            redisTemplate.opsForValue().increment(key);
        }
        
        // 设置响应头
        response.setHeader("X-RateLimit-Limit", String.valueOf(DEFAULT_LIMIT));
        response.setHeader("X-RateLimit-Remaining", String.valueOf(DEFAULT_LIMIT - currentCount - 1));
        
        return true;
    }
    
    private String getClientId(HttpServletRequest request) {
        // 优先使用用户ID,其次使用IP地址
        String userId = (String) request.getAttribute("currentUser");
        if (userId != null) {
            return "user:" + userId;
        }
        
        String clientIp = getClientIpAddress(request);
        return "ip:" + clientIp;
    }
    
    private String getClientIpAddress(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        
        String xRealIp = request.getHeader("X-Real-IP");
        if (xRealIp != null && !xRealIp.isEmpty()) {
            return xRealIp;
        }
        
        return request.getRemoteAddr();
    }
}

2. 跨域处理拦截器

package com.example.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 跨域处理拦截器
 * 处理CORS预检请求和跨域响应头
 */
@Component
public class CorsInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        // 设置CORS响应头
        response.setHeader("Access-Control-Allow-Origin", getAllowedOrigin(request));
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", 
                          "Content-Type, Authorization, X-Requested-With");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Max-Age", "3600");
        
        // 处理OPTIONS预检请求
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return false; // 不继续执行后续处理
        }
        
        return true;
    }
    
    private String getAllowedOrigin(HttpServletRequest request) {
        String origin = request.getHeader("Origin");
        
        // 生产环境应该配置允许的域名列表
        String[] allowedOrigins = {
            "http://localhost:3000",
            "https://example.com",
            "https://admin.example.com"
        };
        
        for (String allowedOrigin : allowedOrigins) {
            if (allowedOrigin.equals(origin)) {
                return origin;
            }
        }
        
        // 默认返回第一个允许的域名或*(谨慎使用)
        return allowedOrigins[0];
    }
}

3. 请求数据验证拦截器

package com.example.interceptor;

import com.example.annotation.ValidateRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Set;

/**
 * 请求数据验证拦截器
 * 提前验证请求数据的有效性
 */
@Component
public class RequestValidationInterceptor implements HandlerInterceptor {
    
    private final ObjectMapper objectMapper;
    private final Validator validator;
    
    public RequestValidationInterceptor(ObjectMapper objectMapper, Validator validator) {
        this.objectMapper = objectMapper;
        this.validator = validator;
    }
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        ValidateRequest validateRequest = AnnotationUtils.findAnnotation(
            handlerMethod.getMethod(), ValidateRequest.class);
        
        if (validateRequest == null) {
            return true; // 没有验证注解,跳过验证
        }
        
        // 读取请求体
        String requestBody = getRequestBody(request);
        if (requestBody.isEmpty()) {
            return true; // 空请求体,跳过验证
        }
        
        try {
            // 解析JSON为指定的对象类型
            Class<?> targetClass = validateRequest.value();
            Object requestObject = objectMapper.readValue(requestBody, targetClass);
            
            // 执行Bean Validation
            Set<ConstraintViolation<Object>> violations = validator.validate(requestObject);
            
            if (!violations.isEmpty()) {
                // 构建错误响应
                StringBuilder errorMessage = new StringBuilder("Validation failed: ");
                for (ConstraintViolation<Object> violation : violations) {
                    errorMessage.append(violation.getPropertyPath())
                               .append(" ")
                               .append(violation.getMessage())
                               .append("; ");
                }
                
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                response.setContentType("application/json");
                response.getWriter().write(String.format(
                    "{\"error\":\"%s\"}", errorMessage.toString()));
                return false;
            }
            
            // 将验证后的对象存储到请求属性中
            request.setAttribute("validatedRequestBody", requestObject);
            
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.setContentType("application/json");
            response.getWriter().write("{\"error\":\"Invalid JSON format\"}");
            return false;
        }
        
        return true;
    }
    
    private String getRequestBody(HttpServletRequest request) throws IOException {
        return StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
    }
}

4. 性能监控拦截器

package com.example.interceptor;

import com.example.service.MetricsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 性能监控拦截器
 * 收集API调用的性能指标
 */
@Component
public class PerformanceMonitorInterceptor implements HandlerInterceptor {
    
    @Autowired
    private MetricsService metricsService;
    
    private static final String START_TIME_ATTRIBUTE = "startTime";
    private static final String METHOD_NAME_ATTRIBUTE = "methodName";
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        long startTime = System.currentTimeMillis();
        request.setAttribute(START_TIME_ATTRIBUTE, startTime);
        
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            String methodName = handlerMethod.getMethod().getDeclaringClass().getSimpleName() 
                               + "." + handlerMethod.getMethod().getName();
            request.setAttribute(METHOD_NAME_ATTRIBUTE, methodName);
        }
        
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, 
                          HttpServletResponse response,
                          Object handler, 
                          ModelAndView modelAndView) throws Exception {
        
        // 记录Controller执行时间
        Long startTime = (Long) request.getAttribute(START_TIME_ATTRIBUTE);
        if (startTime != null) {
            long controllerExecutionTime = System.currentTimeMillis() - startTime;
            request.setAttribute("controllerExecutionTime", controllerExecutionTime);
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response,
                               Object handler, 
                               Exception ex) throws Exception {
        
        Long startTime = (Long) request.getAttribute(START_TIME_ATTRIBUTE);
        String methodName = (String) request.getAttribute(METHOD_NAME_ATTRIBUTE);
        
        if (startTime != null && methodName != null) {
            long totalTime = System.currentTimeMillis() - startTime;
            Long controllerTime = (Long) request.getAttribute("controllerExecutionTime");
            
            // 发送性能指标到监控系统
            metricsService.recordApiCall(
                methodName,
                request.getMethod(),
                request.getRequestURI(),
                response.getStatus(),
                totalTime,
                controllerTime != null ? controllerTime : 0,
                ex != null
            );
        }
    }
}

高级特性

条件拦截器

package com.example.interceptor;

import com.example.annotation.ConditionalInterception;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 条件拦截器基类
 * 根据注解条件决定是否执行拦截逻辑
 */
@Component
public abstract class ConditionalInterceptor implements HandlerInterceptor {
    
    @Override
    public final boolean preHandle(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  Object handler) throws Exception {
        
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        
        // 检查方法级别的条件注解
        ConditionalInterception methodAnnotation = AnnotationUtils.findAnnotation(
            handlerMethod.getMethod(), ConditionalInterception.class);
        
        // 检查类级别的条件注解
        ConditionalInterception classAnnotation = AnnotationUtils.findAnnotation(
            handlerMethod.getBeanType(), ConditionalInterception.class);
        
        // 确定是否应该执行拦截
        if (shouldIntercept(methodAnnotation, classAnnotation, request)) {
            return doPreHandle(request, response, handler);
        }
        
        return true;
    }
    
    /**
     * 判断是否应该拦截当前请求
     */
    protected boolean shouldIntercept(ConditionalInterception methodAnnotation,
                                    ConditionalInterception classAnnotation,
                                    HttpServletRequest request) {
        // 方法级别注解优先级更高
        ConditionalInterception annotation = methodAnnotation != null ? methodAnnotation : classAnnotation;
        
        if (annotation == null) {
            return getDefaultInterceptionBehavior();
        }
        
        return evaluateCondition(annotation, request);
    }
    
    /**
     * 评估拦截条件
     */
    protected boolean evaluateCondition(ConditionalInterception annotation, HttpServletRequest request) {
        // 检查HTTP方法
        if (annotation.methods().length > 0) {
            boolean methodMatched = false;
            for (String method : annotation.methods()) {
                if (method.equalsIgnoreCase(request.getMethod())) {
                    methodMatched = true;
                    break;
                }
            }
            if (!methodMatched) {
                return false;
            }
        }
        
        // 检查请求头
        for (String header : annotation.headers()) {
            if (request.getHeader(header) == null) {
                return false;
            }
        }
        
        // 检查用户角色
        if (annotation.roles().length > 0) {
            String currentUserRole = getCurrentUserRole(request);
            boolean roleMatched = false;
            for (String role : annotation.roles()) {
                if (role.equals(currentUserRole)) {
                    roleMatched = true;
                    break;
                }
            }
            if (!roleMatched) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * 获取当前用户角色
     */
    protected String getCurrentUserRole(HttpServletRequest request) {
        // 从请求属性、Session或JWT Token中获取用户角色
        return (String) request.getAttribute("userRole");
    }
    
    /**
     * 默认拦截行为
     */
    protected boolean getDefaultInterceptionBehavior() {
        return true;
    }
    
    /**
     * 实际的拦截处理逻辑,由子类实现
     */
    protected abstract boolean doPreHandle(HttpServletRequest request, 
                                         HttpServletResponse response, 
                                         Object handler) throws Exception;
}

拦截器链管理

package com.example.interceptor;

import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 动态拦截器链管理器
 * 支持运行时动态添加和移除拦截器
 */
@Component
public class DynamicInterceptorChain {
    
    private final List<HandlerInterceptor> dynamicInterceptors = new CopyOnWriteArrayList<>();
    
    /**
     * 添加动态拦截器
     */
    public void addInterceptor(HandlerInterceptor interceptor) {
        dynamicInterceptors.add(interceptor);
    }
    
    /**
     * 移除动态拦截器
     */
    public void removeInterceptor(HandlerInterceptor interceptor) {
        dynamicInterceptors.remove(interceptor);
    }
    
    /**
     * 执行动态拦截器链
     */
    public boolean executeDynamicChain(HttpServletRequest request, 
                                     HttpServletResponse response, 
                                     Object handler) throws Exception {
        
        for (HandlerInterceptor interceptor : dynamicInterceptors) {
            if (!interceptor.preHandle(request, response, handler)) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * 获取当前动态拦截器数量
     */
    public int getDynamicInterceptorCount() {
        return dynamicInterceptors.size();
    }
    
    /**
     * 清空所有动态拦截器
     */
    public void clearDynamicInterceptors() {
        dynamicInterceptors.clear();
    }
}

性能优化

1. 异步处理支持

package com.example.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 异步请求拦截器
 * 支持异步Servlet处理
 */
@Component
public class AsyncSupportInterceptor implements AsyncHandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        // 标记异步处理开始
        request.setAttribute("asyncStartTime", System.currentTimeMillis());
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, 
                          HttpServletResponse response,
                          Object handler, 
                          ModelAndView modelAndView) throws Exception {
        
        // 同步处理完成
        Long startTime = (Long) request.getAttribute("asyncStartTime");
        if (startTime != null) {
            long duration = System.currentTimeMillis() - startTime;
            request.setAttribute("syncProcessingTime", duration);
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response,
                               Object handler, 
                               Exception ex) throws Exception {
        
        // 整个请求完成(包括异步部分)
        Long startTime = (Long) request.getAttribute("asyncStartTime");
        if (startTime != null) {
            long totalDuration = System.currentTimeMillis() - startTime;
            
            // 记录完整的处理时间
            logRequestCompletion(request, totalDuration, ex);
        }
    }
    
    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, 
                                              HttpServletResponse response,
                                              Object handler) throws Exception {
        
        // 异步处理开始
        request.setAttribute("asyncConcurrentStartTime", System.currentTimeMillis());
    }
    
    private void logRequestCompletion(HttpServletRequest request, long duration, Exception ex) {
        // 实现日志记录逻辑
    }
}

2. 缓存优化拦截器

package com.example.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 响应缓存拦截器
 * 基于HTTP缓存头优化性能
 */
@Component
public class CacheOptimizationInterceptor implements HandlerInterceptor {
    
    private final ConcurrentHashMap<String, String> etagCache = new ConcurrentHashMap<>();
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        // 检查条件请求
        String ifNoneMatch = request.getHeader("If-None-Match");
        String requestKey = generateCacheKey(request);
        
        if (ifNoneMatch != null) {
            String cachedEtag = etagCache.get(requestKey);
            if (ifNoneMatch.equals(cachedEtag)) {
                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                return false; // 返回304,不需要重新处理
            }
        }
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response,
                               Object handler, 
                               Exception ex) throws Exception {
        
        // 为成功响应设置缓存头
        if (ex == null && response.getStatus() == HttpServletResponse.SC_OK) {
            String requestKey = generateCacheKey(request);
            String etag = generateEtag(request, response);
            
            // 设置ETag
            response.setHeader("ETag", etag);
            etagCache.put(requestKey, etag);
            
            // 设置Cache-Control
            if (isCacheable(request)) {
                response.setHeader("Cache-Control", "public, max-age=300"); // 5分钟
            } else {
                response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            }
        }
    }
    
    private String generateCacheKey(HttpServletRequest request) {
        return request.getMethod() + ":" + request.getRequestURI() + ":" + request.getQueryString();
    }
    
    private String generateEtag(HttpServletRequest request, HttpServletResponse response) {
        // 生成基于内容的ETag
        return "\"" + System.currentTimeMillis() + "\"";
    }
    
    private boolean isCacheable(HttpServletRequest request) {
        // 判断请求是否可缓存
        return "GET".equals(request.getMethod()) && 
               !request.getRequestURI().contains("/api/");
    }
}

最佳实践

1. 拦截器设计原则

单一职责原则

// ✅ 好的做法:每个拦截器只处理一种关注点
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    // 只负责身份验证
}

@Component  
public class AuthorizationInterceptor implements HandlerInterceptor {
    // 只负责权限检查
}

@Component
public class LoggingInterceptor implements HandlerInterceptor {
    // 只负责日志记录
}

// ❌ 不好的做法:一个拦截器处理多种关注点
@Component
public class MegaInterceptor implements HandlerInterceptor {
    // 混合了认证、授权、日志、限流等多种职责
}

可配置性设计

@Component
@ConfigurationProperties(prefix = "app.interceptor.auth")
public class ConfigurableAuthInterceptor implements HandlerInterceptor {
    
    private boolean enabled = true;
    private List<String> excludePaths = new ArrayList<>();
    private int tokenExpirationMinutes = 60;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        if (!enabled) {
            return true; // 拦截器被禁用
        }
        
        String requestPath = request.getRequestURI();
        if (excludePaths.stream().anyMatch(requestPath::startsWith)) {
            return true; // 路径被排除
        }
        
        // 执行认证逻辑
        return performAuthentication(request, response);
    }
    
    // Getters and Setters
}

2. 错误处理策略

@Component
public class RobustInterceptor implements HandlerInterceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(RobustInterceptor.class);
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        try {
            // 执行拦截逻辑
            return doBusinessLogic(request, response, handler);
            
        } catch (BusinessException e) {
            // 业务异常,返回明确的错误信息
            logger.warn("Business exception in interceptor: {}", e.getMessage());
            sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
            return false;
            
        } catch (Exception e) {
            // 系统异常,记录详细日志但不暴露内部信息
            logger.error("Unexpected exception in interceptor", e);
            sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
                             "Internal server error");
            return false;
        }
    }
    
    private void sendErrorResponse(HttpServletResponse response, int status, String message) 
            throws IOException {
        response.setStatus(status);
        response.setContentType("application/json");
        response.getWriter().write(String.format("{\"error\":\"%s\"}", message));
    }
    
    private boolean doBusinessLogic(HttpServletRequest request, 
                                   HttpServletResponse response, 
                                   Object handler) throws Exception {
        // 实际的业务逻辑
        return true;
    }
}

3. 测试友好设计

@Component
public class TestableInterceptor implements HandlerInterceptor {
    
    private final AuthService authService;
    private final Clock clock; // 可注入的时钟,便于测试
    
    public TestableInterceptor(AuthService authService, Clock clock) {
        this.authService = authService;
        this.clock = clock;
    }
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        String token = extractToken(request);
        if (token == null) {
            return handleMissingToken(response);
        }
        
        return validateToken(token, request, response);
    }
    
    // 包级别可见性,便于单元测试
    String extractToken(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }
    
    boolean validateToken(String token, HttpServletRequest request, HttpServletResponse response) 
            throws Exception {
        try {
            boolean isValid = authService.validateToken(token, clock.instant());
            if (!isValid) {
                return handleInvalidToken(response);
            }
            
            // 将用户信息存储到请求属性
            String userId = authService.getUserIdFromToken(token);
            request.setAttribute("currentUserId", userId);
            
            return true;
            
        } catch (Exception e) {
            return handleTokenValidationError(response, e);
        }
    }
    
    private boolean handleMissingToken(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("{\"error\":\"Missing authentication token\"}");
        return false;
    }
    
    private boolean handleInvalidToken(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("{\"error\":\"Invalid or expired token\"}");
        return false;
    }
    
    private boolean handleTokenValidationError(HttpServletResponse response, Exception e) 
            throws IOException {
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        response.getWriter().write("{\"error\":\"Token validation failed\"}");
        return false;
    }
}

4. 单元测试示例

@ExtendWith(MockitoExtension.class)
class TestableInterceptorTest {
    
    @Mock
    private AuthService authService;
    
    @Mock
    private HttpServletRequest request;
    
    @Mock
    private HttpServletResponse response;
    
    @Mock
    private PrintWriter writer;
    
    private TestableInterceptor interceptor;
    private Clock fixedClock;
    
    @BeforeEach
    void setUp() throws IOException {
        fixedClock = Clock.fixed(Instant.parse("2023-01-01T10:00:00Z"), ZoneOffset.UTC);
        interceptor = new TestableInterceptor(authService, fixedClock);
        when(response.getWriter()).thenReturn(writer);
    }
    
    @Test
    void testPreHandle_WithValidToken_ShouldReturnTrue() throws Exception {
        // Given
        String token = "valid-token";
        when(request.getHeader("Authorization")).thenReturn("Bearer " + token);
        when(authService.validateToken(token, fixedClock.instant())).thenReturn(true);
        when(authService.getUserIdFromToken(token)).thenReturn("user123");
        
        // When
        boolean result = interceptor.preHandle(request, response, null);
        
        // Then
        assertTrue(result);
        verify(request).setAttribute("currentUserId", "user123");
    }
    
    @Test
    void testPreHandle_WithMissingToken_ShouldReturnFalse() throws Exception {
        // Given
        when(request.getHeader("Authorization")).thenReturn(null);
        
        // When
        boolean result = interceptor.preHandle(request, response, null);
        
        // Then
        assertFalse(result);
        verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        verify(writer).write("{\"error\":\"Missing authentication token\"}");
    }
    
    @Test
    void testExtractToken_WithValidAuthHeader_ShouldReturnToken() {
        // Given
        when(request.getHeader("Authorization")).thenReturn("Bearer abc123");
        
        // When
        String token = interceptor.extractToken(request);
        
        // Then
        assertEquals("abc123", token);
    }
    
    @Test
    void testExtractToken_WithInvalidAuthHeader_ShouldReturnNull() {
        // Given
        when(request.getHeader("Authorization")).thenReturn("Basic abc123");
        
        // When
        String token = interceptor.extractToken(request);
        
        // Then
        assertNull(token);
    }
}

常见问题

问题1:拦截器不生效

❓ 现象: 配置了拦截器但没有被执行

解决方案:

  1. 检查组件注册:

    // 确保拦截器被Spring管理
    @Component // 或 @Bean
    public class MyInterceptor implements HandlerInterceptor {
        // ...
    }
  2. 检查配置注册:

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
        
        @Autowired
        private MyInterceptor myInterceptor;
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(myInterceptor)
                    .addPathPatterns("/**"); // 确保路径模式正确
        }
    }
  3. 检查路径匹配:

    // 确保URL路径匹配规则正确
    registry.addInterceptor(interceptor)
            .addPathPatterns("/api/**")          // 匹配所有/api/开头的路径
            .excludePathPatterns("/api/public/**"); // 排除公共路径

问题2:拦截器执行顺序混乱

❓ 现象: 多个拦截器的执行顺序不符合预期

解决方案:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    // 使用order()方法指定执行顺序(数字越小,优先级越高)
    registry.addInterceptor(corsInterceptor)
            .addPathPatterns("/**")
            .order(1); // 最先执行
    
    registry.addInterceptor(authenticationInterceptor)
            .addPathPatterns("/api/**")
            .order(2); // 第二执行
    
    registry.addInterceptor(loggingInterceptor)
            .addPathPatterns("/**")
            .order(3); // 最后执行
}

问题3:拦截器中的依赖注入失败

❓ 现象: 拦截器中使用@Autowired注入的服务为null

解决方案:

// ❌ 错误方式:手动new创建拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new MyInterceptor()); // 依赖注入不会生效
}

// ✅ 正确方式:注入Spring管理的Bean
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private MyInterceptor myInterceptor; // Spring管理的Bean
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor); // 依赖注入正常工作
    }
}

问题4:拦截器中的异常处理

❓ 现象: 拦截器抛出异常后,后续处理流程异常

解决方案:

@Component
public class SafeInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        try {
            // 可能抛出异常的逻辑
            return performBusinessLogic(request, response);
            
        } catch (Exception e) {
            // 记录异常日志
            logger.error("Interceptor exception", e);
            
            // 设置错误响应
            if (!response.isCommitted()) {
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                response.getWriter().write("{\"error\":\"Internal server error\"}");
            }
            
            return false; // 阻止后续处理
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response,
                               Object handler, 
                               Exception ex) throws Exception {
        
        // afterCompletion方法即使前面有异常也会被调用
        // 在这里进行清理工作
        try {
            performCleanup(request);
        } catch (Exception cleanupEx) {
            // 清理异常不应该影响主流程
            logger.warn("Cleanup exception in interceptor", cleanupEx);
        }
    }
}

问题5:静态资源被拦截

❓ 现象: CSS、JS、图片等静态资源被拦截器处理

解决方案:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(authInterceptor)
            .addPathPatterns("/**")
            .excludePathPatterns(
                "/static/**",        // 排除静态资源
                "/css/**",
                "/js/**", 
                "/images/**",
                "/favicon.ico",
                "/error",            // 排除错误页面
                "/actuator/**"       // 排除监控端点
            );
}

// 或者配置静态资源处理器
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**")
            .addResourceLocations("classpath:/static/");
}

相关文章

Spring MVC系列

Spring框架基础

  • Spring基本应用 - Spring容器和IoC
  • Spring依赖注入 - DI机制详解
  • Spring AOP - 面向切面编程
  • Spring事务管理 - 声明式事务

Spring Boot集成

企业级开发

相关技术


总结

Spring MVC拦截器是构建企业级Web应用的重要组件,为开发者提供了强大而灵活的请求处理机制。

🎯 核心价值

  • 非侵入性: 无需修改现有业务代码即可添加横切关注点
  • 高度可配置: 支持精确的URL路径匹配和条件拦截
  • Spring集成: 与Spring生态系统无缝集成,支持依赖注入
  • 性能优化: 提供多个拦截点,支持细粒度的性能控制

🛠️ 技术要点

  1. 接口设计: HandlerInterceptor提供三个关键拦截点
  2. 配置管理: 支持XML和Java配置两种方式
  3. 执行顺序: 通过order()方法精确控制执行顺序
  4. 异常处理: 提供完善的异常处理和恢复机制
  5. 性能优化: 支持异步处理和缓存优化

🚀 应用场景

  • 身份认证: JWT Token验证、Session管理
  • 权限控制: 基于角色的访问控制(RBAC)
  • API限流: 防止接口被恶意调用
  • 日志审计: 记录请求详情和性能指标
  • 跨域处理: CORS策略实施
  • 缓存优化: HTTP缓存头管理

📈 最佳实践

  1. 单一职责: 每个拦截器只处理一种关注点
  2. 可配置性: 支持运行时配置和条件启用
  3. 错误处理: 提供健壮的异常处理机制
  4. 测试友好: 设计易于单元测试的拦截器
  5. 性能考虑: 避免在拦截器中执行耗时操作