概述
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:拦截器不生效
❓ 现象: 配置了拦截器但没有被执行
解决方案:
-
检查组件注册:
// 确保拦截器被Spring管理 @Component // 或 @Bean public class MyInterceptor implements HandlerInterceptor { // ... } -
检查配置注册:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private MyInterceptor myInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(myInterceptor) .addPathPatterns("/**"); // 确保路径模式正确 } } -
检查路径匹配:
// 确保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 MVC 核心架构 - MVC框架基础
- Spring MVC 核心类和注解 - 核心组件详解
- 数据绑定机制 - 请求参数处理
- JSON数据和RESTful风格URL - REST API开发
Spring框架基础
- Spring基本应用 - Spring容器和IoC
- Spring依赖注入 - DI机制详解
- Spring AOP - 面向切面编程
- Spring事务管理 - 声明式事务
Spring Boot集成
- SpringBoot项目结构 - SpringBoot基础
企业级开发
- 文件上传和下载 - 文件处理
- MyBatis持久层 - 数据持久化
- Spring和MyBatis整合 - 框架集成
相关技术
- C++ 编程语言完整指南 - 系统级编程
- Python 编程语言完整指南 - 脚本开发
- 微信小程序开发指南 - 移动端开发
总结
Spring MVC拦截器是构建企业级Web应用的重要组件,为开发者提供了强大而灵活的请求处理机制。
🎯 核心价值
- 非侵入性: 无需修改现有业务代码即可添加横切关注点
- 高度可配置: 支持精确的URL路径匹配和条件拦截
- Spring集成: 与Spring生态系统无缝集成,支持依赖注入
- 性能优化: 提供多个拦截点,支持细粒度的性能控制
🛠️ 技术要点
- 接口设计: HandlerInterceptor提供三个关键拦截点
- 配置管理: 支持XML和Java配置两种方式
- 执行顺序: 通过order()方法精确控制执行顺序
- 异常处理: 提供完善的异常处理和恢复机制
- 性能优化: 支持异步处理和缓存优化
🚀 应用场景
- 身份认证: JWT Token验证、Session管理
- 权限控制: 基于角色的访问控制(RBAC)
- API限流: 防止接口被恶意调用
- 日志审计: 记录请求详情和性能指标
- 跨域处理: CORS策略实施
- 缓存优化: HTTP缓存头管理
📈 最佳实践
- 单一职责: 每个拦截器只处理一种关注点
- 可配置性: 支持运行时配置和条件启用
- 错误处理: 提供健壮的异常处理机制
- 测试友好: 设计易于单元测试的拦截器
- 性能考虑: 避免在拦截器中执行耗时操作