在Web应用开发中,拦截器(Interceptor)是处理横切关注点(Cross-Cutting Concerns)的利器。不同于传统的面向对象编程方式需要在每个业务方法中重复编写相同逻辑,拦截器通过AOP思想实现了关注点分离。Spring MVC的拦截器机制基于责任链模式(Chain of Responsibility Pattern)设计,允许开发者定义多个拦截器形成处理链条,每个拦截器都能在请求处理的不同阶段介入。
想象你正在开发一个电商平台的后端系统。几乎每个接口都需要:
如果把这些逻辑都写在Controller方法里,代码会变成这样:
java复制@PostMapping("/order")
public Result createOrder(@RequestBody OrderDTO dto) {
// 1. 权限校验
if (!checkToken(request.getHeader("token"))) {
return Result.fail("未授权");
}
// 2. 日志记录
log.info("请求参数:{}", dto);
long start = System.currentTimeMillis();
try {
// 3. 业务逻辑
Order order = orderService.create(dto);
// 4. 响应包装
return Result.success(order);
} finally {
// 5. 性能统计
long cost = System.currentTimeMillis() - start;
log.info("接口耗时:{}ms", cost);
}
}
这种写法存在三个致命问题:
拦截器正是为了解决这些问题而生。通过拦截器,我们可以将上述通用逻辑抽取出来,形成可复用的组件:
java复制public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 统一权限校验
return checkToken(request.getHeader("token"));
}
}
public class LogInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 统一日志记录
logRequest(request);
logResponse(response);
}
}
很多初学者容易混淆拦截器(Interceptor)和过滤器(Filter),它们确实都能对请求进行拦截处理,但存在本质区别:
| 特性 | Servlet Filter | Spring Interceptor |
|---|---|---|
| 所属层次 | Servlet规范 | Spring MVC框架 |
| 执行时机 | 在Servlet容器层面,DispatcherServlet之前 | 在DispatcherServlet内部 |
| 依赖关系 | 不依赖Spring容器 | 由Spring容器管理 |
| 访问范围 | 只能获取ServletRequest/ServletResponse | 可以获取HandlerMethod等Spring对象 |
| 异常处理 | 无法使用Spring的异常处理机制 | 可以配合@ControllerAdvice统一处理 |
| 配置方式 | web.xml或@WebFilter注解 | 实现HandlerInterceptor接口+配置类注册 |
关键理解:Filter是Servlet层面的"大门保安",而Interceptor是Spring MVC内部的"安检人员"。一个请求会先经过Filter链,再进入Interceptor链,最后到达Controller。
Spring MVC的拦截器需要实现HandlerInterceptor接口,该接口定义了三个关键方法:
java复制public interface HandlerInterceptor {
// 在Controller方法执行前调用
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
return true;
}
// 在Controller方法执行后,视图渲染前调用
default void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
}
// 在整个请求完成后调用(视图渲染完毕)
default void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
}
}
true:继续执行下一个拦截器或Controllerfalse:中断执行,不会调用后续拦截器和Controllercode复制请求进入
│
▼
[FilterChain]
│
▼
[DispatcherServlet]
│
▼
Interceptor1.preHandle()
│
▼
Interceptor2.preHandle()
│
▼
Controller方法执行
│
▼
Interceptor2.postHandle()
│
▼
Interceptor1.postHandle()
│
▼
视图渲染
│
▼
Interceptor2.afterCompletion()
│
▼
Interceptor1.afterCompletion()
│
▼
响应返回
重要规则:
- preHandle按拦截器注册顺序正向执行
- postHandle和afterCompletion按拦截器注册顺序逆向执行
- 只要有一个preHandle返回false,后续拦截器和Controller都不会执行
- afterCompletion总会执行(类似try-finally机制)
java复制public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (!token.startsWith("Bearer ")) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"code\":401,\"message\":\"无效的Token格式\"}");
return false;
}
User user = jwtService.verifyToken(token.substring(7));
if (user == null) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"code\":401,\"message\":\"Token已过期或无效\"}");
return false;
}
// 将用户信息存入请求属性
request.setAttribute("currentUser", user);
return true;
}
}
java复制public class LogInterceptor implements HandlerInterceptor {
private static final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
startTime.set(System.currentTimeMillis());
log.info("请求开始 => URI: {}, Method: {}, IP: {}, Params: {}",
request.getRequestURI(),
request.getMethod(),
request.getRemoteAddr(),
getRequestParams(request));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long cost = System.currentTimeMillis() - startTime.get();
startTime.remove();
log.info("请求结束 => URI: {}, Status: {}, Cost: {}ms, Exception: {}",
request.getRequestURI(),
response.getStatus(),
cost,
ex != null ? ex.getMessage() : "null");
}
private String getRequestParams(HttpServletRequest request) {
// 获取请求参数的实现...
}
}
java复制public class RateLimitInterceptor implements HandlerInterceptor {
private final RateLimiter rateLimiter = RateLimiter.create(100); // 100请求/秒
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!rateLimiter.tryAcquire()) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁\"}");
return false;
}
return true;
}
}
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注意注册顺序决定了执行顺序
registry.addInterceptor(new RateLimitInterceptor())
.addPathPatterns("/api/**")
.order(1); // 最先执行
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login")
.order(2);
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**")
.order(3); // 最后执行
}
}
java复制@RestController
@RequestMapping("/api")
public class TestController {
@GetMapping("/test")
public String test() {
return "Hello Interceptor!";
}
}
code复制GET /api/test
Headers:
Authorization: Bearer valid_token
控制台输出:
code复制[RateLimitInterceptor] 通过限流检查
[AuthInterceptor] 用户认证成功
[LogInterceptor] 请求开始 => URI: /api/test, Method: GET...
[TestController] 执行业务逻辑
[LogInterceptor] 请求结束 => URI: /api/test, Status: 200, Cost: 15ms...
code复制GET /api/test
控制台输出:
code复制[RateLimitInterceptor] 请求过于频繁,已拦截
[LogInterceptor] 请求结束 => URI: /api/test, Status: 429, Cost: 0ms...
注意:AuthInterceptor和Controller都没有执行
在Spring MVC的异步请求(如Callable、DeferredResult)场景下,拦截器的执行有一些特殊行为:
如果需要访问异步上下文,可以这样处理:
java复制public class AsyncInterceptor implements HandlerInterceptor {
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 当Controller返回Callable/DeferredResult时触发
log.info("异步请求开始处理");
}
}
java复制public class CachingAuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
Boolean authenticated = (Boolean) request.getAttribute("_auth_cached");
if (authenticated != null) {
return authenticated;
}
// 实际认证逻辑
boolean result = doAuth(request);
request.setAttribute("_auth_cached", result);
return result;
}
}
虽然拦截器和AOP(如@Around)都能实现横切逻辑,但它们各有适用场景:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 请求级别的预处理 | 拦截器 | 可以访问HttpServletRequest/Response |
| 方法级别的切面 | AOP | 更精细的方法拦截,可以获取方法参数和注解 |
| 响应包装 | 两者均可 | 拦截器用postHandle,AOP用@Around |
| 异常处理 | @ControllerAdvice | 统一异常处理机制更强大 |
典型协作案例:
java复制@Aspect
@Component
public class CacheAspect {
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public Object cacheAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 缓存逻辑...
}
}
检查是否注册成功
检查路径匹配
/mappings端点查看拦截器注册情况检查执行顺序
检查Filter的影响
当同时使用CORS和拦截器时,需要注意:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login");
}
}
拦截器内抛出的异常不会被@ControllerAdvice捕获,需要特殊处理:
java复制public class SafeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
try {
// 业务逻辑
return true;
} catch (Exception e) {
response.setStatus(500);
response.getWriter().write("{\"error\":\"" + e.getMessage() + "\"}");
return false;
}
}
}
Spring拦截器链是责任链模式的经典实现。我们可以扩展这种模式来实现更灵活的拦截逻辑:
java复制public class CompositeInterceptor implements HandlerInterceptor {
private final List<HandlerInterceptor> interceptors;
public CompositeInterceptor(List<HandlerInterceptor> interceptors) {
this.interceptors = interceptors;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
for (HandlerInterceptor interceptor : interceptors) {
if (!interceptor.preHandle(request, response, handler)) {
return false;
}
}
return true;
}
// 类似实现postHandle和afterCompletion...
}
对于有共同处理逻辑的拦截器,可以使用模板方法模式:
java复制public abstract class AbstractLogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
logRequest(request);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
long cost = System.currentTimeMillis() - (long) request.getAttribute("startTime");
logResponse(request, response, cost, ex);
}
protected abstract void logRequest(HttpServletRequest request);
protected abstract void logResponse(HttpServletRequest request,
HttpServletResponse response,
long cost,
Exception ex);
}
在微服务架构下,拦截器的使用场景发生了变化:
java复制public class FeignAuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String token = getCurrentToken();
template.header("Authorization", "Bearer " + token);
}
}
尽管如此,Spring MVC拦截器在微服务内部的API处理中仍然扮演重要角色,特别是对于:
等场景仍然是最佳选择。