一、拦截器基础概念
1.1 什么是拦截器?
拦截器(Interceptor)是Spring MVC框架提供的一种机制,允许你在请求处理的不同阶段进行拦截和处理。它类似于Servlet中的过滤器(Filter),但提供了更精细的控制和更丰富的功能。
通俗理解:想象你去银行办理业务,拦截器就像是银行大厅的各个服务环节:
- 取号机:验证你是否是客户(预处理)
- 大堂经理:检查你的材料是否齐全(预处理)
- 柜台人员:处理你的核心业务(业务处理)
- 评价器:询问你对服务的满意度(后处理)
1.2 拦截器 vs 过滤器
对比维度 | 拦截器(Interceptor) | 过滤器(Filter) |
---|---|---|
归属 | Spring框架的一部分 | Servlet规范的一部分 |
依赖 | 依赖于Spring容器 | 不依赖任何框架 |
实现方式 | 基于Java反射和动态代理 | 基于函数回调 |
使用场景 | 处理Spring相关的逻辑(如Controller调用) | 处理更底层的请求/响应 |
获取Bean | 可以直接获取Spring容器中的Bean | 无法直接获取Spring容器中的Bean |
执行顺序 | 在DispatcherServlet之后执行 | 在DispatcherServlet之前执行 |
细粒度 | 更细,可以精确到具体的Controller方法 | 较粗,基于URL模式匹配 |
异常处理 | 可以结合Spring的异常处理机制 | 只能自行处理 |
1.3 拦截器的执行流程
客户端请求
↓
DispatcherServlet
↓
Interceptor.preHandle() → 返回false则流程终止
↓
Controller方法执行
↓
Interceptor.postHandle()
↓
视图渲染(如果有)
↓
Interceptor.afterCompletion()
二、拦截器核心方法与生命周期
2.1 拦截器的三个核心方法
方法名 | 执行时机 | 返回值 | 典型应用场景 |
---|---|---|---|
preHandle | Controller方法执行前 | boolean | 权限验证、日志记录、参数预处理 |
postHandle | Controller方法执行后,视图渲染前 | void | 修改模型数据、添加通用属性 |
afterCompletion | 整个请求完成后(视图渲染完毕) | void | 资源清理、性能监控、异常记录 |
2.2 方法详细解析
2.2.1 preHandle 方法
/**
* 在Controller方法执行前调用
* @param request 当前HTTP请求
* @param response 当前HTTP响应
* @param handler 将被执行的处理器对象(通常是HandlerMethod)
* @return 返回true则继续执行,返回false则中断流程
* @throws Exception 可以抛出异常
*/
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
return true;
}
日常例子:就像进入公司大门需要刷卡,preHandle就是门禁系统:
- 卡有效(preHandle返回true):允许进入
- 卡无效(preHandle返回false):拒绝进入
2.2.2 postHandle 方法
/**
* 在Controller方法执行后,视图渲染前调用
* @param request 当前HTTP请求
* @param response 当前HTTP响应
* @param handler 被执行的处理器对象
* @param modelAndView 控制器返回的ModelAndView(可能为null)
* @throws Exception 可以抛出异常
*/
default void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
}
日常例子:就像厨师做完菜后,服务员在上菜前对菜品进行最后的检查装饰。
2.2.3 afterCompletion 方法
/**
* 在整个请求完成后调用(视图渲染完毕)
* 通常用于资源清理
* @param request 当前HTTP请求
* @param response 当前HTTP响应
* @param handler 被执行的处理器对象
* @param ex 处理过程中抛出的异常(如果有)
* @throws Exception 可以抛出异常
*/
default void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
}
日常例子:就像餐厅客人离开后,服务员清理桌面准备迎接下一位客人。
2.3 多个拦截器的执行顺序
当配置多个拦截器时,它们的执行顺序如下:
- preHandle按配置顺序执行
- postHandle按配置逆序执行
- afterCompletion按配置逆序执行
记忆口诀:preHandle顺序喊"到",postHandle反着来,afterCompletion跟着postHandle走。
三、拦截器实战开发
3.1 创建自定义拦截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 日志拦截器:记录请求处理全过程的耗时
*/
public class LogInterceptor implements HandlerInterceptor {
// 线程局部变量,用于记录开始时间
private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 记录请求开始时间
long startTime = System.currentTimeMillis();
startTimeThreadLocal.set(startTime);
// 打印请求信息
System.out.println("[" + startTime + "] 请求开始: " +
request.getRequestURI() +
", 参数: " + request.getQueryString());
// 模拟权限检查
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"未授权\"}");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("请求处理完成,准备渲染视图");
// 可以在这里修改ModelAndView
if (modelAndView != null) {
modelAndView.addObject("interceptorMsg", "来自拦截器的消息");
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 获取请求开始时间
long startTime = startTimeThreadLocal.get();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// 打印耗时
System.out.println("[" + endTime + "] 请求结束: " +
request.getRequestURI() +
", 耗时: " + duration + "ms");
// 清理线程局部变量
startTimeThreadLocal.remove();
// 如果有异常,可以在这里记录
if (ex != null) {
System.err.println("请求处理出现异常: " + ex.getMessage());
}
}
}
3.2 注册拦截器
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册日志拦截器,并配置拦截路径和排除路径
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login", "/error"); // 排除登录和错误页面
// 可以注册多个拦截器
// registry.addInterceptor(new AnotherInterceptor())...
// 设置拦截器顺序(值越小优先级越高)
// registry.addInterceptor(new FirstInterceptor()).order(1);
// registry.addInterceptor(new SecondInterceptor()).order(2);
}
}
3.3 测试Controller
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/hello")
public String hello(@RequestHeader(value = "Authorization", required = false) String token) {
System.out.println("Controller方法执行中...");
System.out.println("Token: " + token);
return "Hello, Spring Boot Interceptor!";
}
@GetMapping("/login")
public String login() {
return "Login Page";
}
}
四、拦截器高级应用
4.1 权限验证拦截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 从请求头获取token
String token = request.getHeader("Authorization");
// 模拟验证token
if (token == null || !token.equals("Bearer mock-token-123456")) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"code\":401,\"message\":\"无效的Token\"}");
return false;
}
// 验证通过,可以在这里设置用户信息到请求属性中
request.setAttribute("currentUser", "user123");
return true;
}
}
4.2 接口限流拦截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.springframework.web.servlet.HandlerInterceptor;
public class RateLimitInterceptor implements HandlerInterceptor {
// 使用ConcurrentHashMap存储每个IP的访问次数和时间
private final ConcurrentHashMap<String, RateLimitRecord> rateLimitMap = new ConcurrentHashMap<>();
// 限制配置:每秒最多5次请求
private static final int MAX_REQUESTS = 5;
private static final long TIME_WINDOW = 1000; // 1秒
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String ip = request.getRemoteAddr();
long currentTime = System.currentTimeMillis();
// 获取或创建记录
RateLimitRecord record = rateLimitMap.computeIfAbsent(
ip, k -> new RateLimitRecord(currentTime, 0));
// 如果时间窗口已过,重置计数
if (currentTime - record.getTimestamp() > TIME_WINDOW) {
record.setTimestamp(currentTime);
record.setCount(0);
}
// 检查请求次数
if (record.getCount() >= MAX_REQUESTS) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁,请稍后再试\"}");
return false;
}
// 增加计数
record.incrementCount();
return true;
}
// 内部类,用于记录请求次数和时间戳
private static class RateLimitRecord {
private long timestamp;
private int count;
public RateLimitRecord(long timestamp, int count) {
this.timestamp = timestamp;
this.count = count;
}
// getter和setter方法
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public int getCount() { return count; }
public void setCount(int count) { this.count = count; }
public void incrementCount() { this.count++; }
}
}
4.3 数据脱敏拦截器
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
public class DataMaskingInterceptor implements HandlerInterceptor {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
if (modelAndView != null && modelAndView.getModel() != null) {
Map<String, Object> model = modelAndView.getModel();
Map<String, Object> maskedModel = new HashMap<>();
// 遍历模型数据,对敏感信息进行脱敏处理
for (Map.Entry<String, Object> entry : model.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
// 简单演示:对包含"phone"或"idCard"的字段进行脱敏
if (entry.getKey().toLowerCase().contains("phone")) {
maskedModel.put(entry.getKey(), maskPhone((String) value));
} else if (entry.getKey().toLowerCase().contains("idcard")) {
maskedModel.put(entry.getKey(), maskIdCard((String) value));
} else {
maskedModel.put(entry.getKey(), value);
}
} else {
maskedModel.put(entry.getKey(), value);
}
}
// 替换原始模型
modelAndView.getModel().clear();
modelAndView.getModel().putAll(maskedModel);
}
}
private String maskPhone(String phone) {
if (phone == null || phone.length() < 7) return phone;
return phone.substring(0, 3) + "****" + phone.substring(7);
}
private String maskIdCard(String idCard) {
if (idCard == null || idCard.length() < 15) return idCard;
return idCard.substring(0, 6) + "********" + idCard.substring(14);
}
}
五、拦截器进阶技巧
5.1 拦截器与异常处理
拦截器可以与Spring的异常处理机制结合使用:
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ExceptionInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
if (ex != null) {
// 根据异常类型进行不同处理
if (ex instanceof IllegalArgumentException) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("{\"code\":400,\"message\":\"参数错误: " + ex.getMessage() + "\"}");
}
// 可以添加更多异常类型的处理...
}
}
}
5.2 拦截器与异步请求
对于异步请求(如使用@Async或WebAsyncTask),拦截器的行为有所不同:
- preHandle: 在异步线程开始前执行
- postHandle和afterCompletion: 默认不会执行
要让拦截器支持异步请求,需要实现AsyncHandlerInterceptor接口:
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AsyncInterceptor implements AsyncHandlerInterceptor {
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 这个方法会在异步处理开始时调用
System.out.println("异步请求开始处理: " + request.getRequestURI());
}
// 其他方法...
}
5.3 拦截器执行顺序控制
在Spring Boot中,可以通过实现Ordered接口或使用@Order注解来控制拦截器的执行顺序:
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.HandlerInterceptor;
@Order(1) // 值越小优先级越高
public class FirstInterceptor implements HandlerInterceptor {
// 实现方法...
}
@Order(2)
public class SecondInterceptor implements HandlerInterceptor {
// 实现方法...
}
或者在注册时指定顺序:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FirstInterceptor()).order(1);
registry.addInterceptor(new SecondInterceptor()).order(2);
}
六、拦截器最佳实践
6.1 性能监控拦截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DecimalFormat;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.servlet.HandlerInterceptor;
public class PerformanceInterceptor implements HandlerInterceptor {
private final AtomicInteger requestCount = new AtomicInteger(0);
private final AtomicLong totalTime = new AtomicLong(0);
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
startTime.set(System.currentTimeMillis());
requestCount.incrementAndGet();
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
long duration = System.currentTimeMillis() - startTime.get();
totalTime.addAndGet(duration);
startTime.remove();
// 每100次请求打印一次性能统计
if (requestCount.get() % 100 == 0) {
double avgTime = (double) totalTime.get() / requestCount.get();
DecimalFormat df = new DecimalFormat("#.##");
System.out.println("\n===== 性能统计 =====");
System.out.println("总请求数: " + requestCount.get());
System.out.println("总处理时间: " + totalTime.get() + "ms");
System.out.println("平均处理时间: " + df.format(avgTime) + "ms");
System.out.println("当前请求[" + request.getRequestURI() + "]耗时: " + duration + "ms");
System.out.println("==================\n");
}
}
}
6.2 API版本控制拦截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class ApiVersionInterceptor implements HandlerInterceptor {
private static final String VERSION_HEADER = "X-API-Version";
private static final String DEFAULT_VERSION = "1.0";
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String version = request.getHeader(VERSION_HEADER);
if (version == null) {
version = DEFAULT_VERSION;
}
// 将版本信息设置到请求属性中,供Controller使用
request.setAttribute("apiVersion", version);
// 可以根据版本号做不同的处理逻辑
if ("1.0".equals(version)) {
// 旧版本逻辑
} else if ("2.0".equals(version)) {
// 新版本逻辑
}
return true;
}
}
6.3 多租户拦截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class TenantInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 从请求头、Cookie或URL参数中获取租户ID
String tenantId = request.getHeader("X-Tenant-ID");
if (tenantId == null) {
tenantId = request.getParameter("tenantId");
}
// 验证租户ID是否有效
if (tenantId == null || tenantId.isEmpty()) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("{\"code\":400,\"message\":\"缺少租户标识\"}");
return false;
}
// 将租户信息存储到当前线程中(可以使用ThreadLocal)
TenantContext.setCurrentTenant(tenantId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 清除线程中的租户信息
TenantContext.clear();
}
}
// 租户上下文类
class TenantContext {
private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
public static void setCurrentTenant(String tenant) {
currentTenant.set(tenant);
}
public static String getCurrentTenant() {
return currentTenant.get();
}
public static void clear() {
currentTenant.remove();
}
}
七、常见问题与解决方案
7.1 拦截器不生效的常见原因
- 拦截器未注册:忘记在配置类中调用
addInterceptor
方法 - 路径配置错误:
addPathPatterns
和excludePathPatterns
配置不正确 - 顺序问题:被其他拦截器提前终止
- Spring Boot版本问题:某些版本可能有不同的默认配置
- 拦截器Bean未正确初始化:如果拦截器中有依赖注入,确保它是Spring管理的Bean
7.2 拦截器中的依赖注入
如果需要在拦截器中使用Spring管理的Bean,可以将拦截器本身也声明为Bean:
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
// 实现方法...
}
// 然后在配置类中注入拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor);
}
}
7.3 拦截器与跨域(CORS)处理
拦截器和CORS处理器的执行顺序需要注意:
- 对于简单请求(Simple Request),CORS过滤器先执行
- 对于非简单请求,会先发送OPTIONS预检请求
如果拦截器拦截了OPTIONS请求,可能导致CORS失败。解决方案:
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 放行OPTIONS请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
// 其他处理逻辑...
}
八、总结
8.1 拦截器核心要点总结
- 三个核心方法:preHandle、postHandle、afterCompletion
- 执行顺序:多个拦截器时,preHandle顺序执行,postHandle和afterCompletion逆序执行
- 应用场景:权限验证、日志记录、性能监控、数据脱敏等
- 与过滤器区别:拦截器更上层,能获取Spring上下文,执行时机不同
- 最佳实践:保持拦截器职责单一,注意性能影响,合理处理异常
关注不关注,你自己决定(但正确的决定只有一个)。
喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!