一、拦截器基础概念
1.1 什么是拦截器?
拦截器(Interceptor)是Spring MVC框架中的一种核心组件,它可以在请求到达Controller之前和视图渲染之后对请求进行拦截处理。与过滤器(Filter)不同,拦截器工作在Spring的上下文环境中,能够直接使用Spring的依赖注入等功能。
1.2 拦截器 vs 过滤器
特性 | 拦截器(Interceptor) | 过滤器(Filter) |
---|---|---|
工作层次 | Spring MVC层面 | Servlet容器层面 |
依赖关系 | 依赖Spring容器 | 不依赖Spring |
执行时机 | Controller方法前后 | Servlet请求处理前后 |
访问对象 | 可以获取HandlerMethod信息 | 只能获取Servlet API对象 |
异常处理 | 可以接入Spring的异常处理机制 | 无法使用Spring的异常处理 |
配置方式 | 实现接口+注册 | @WebFilter或web.xml配置 |
二、拦截器核心实现
2.1 创建自定义拦截器
实现HandlerInterceptor
接口的三个核心方法:
@Component
public class CustomInterceptor implements HandlerInterceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 预处理回调方法(Controller方法执行前)
* 返回true表示继续流程,false表示中断
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
logger.info(">>> preHandle: {}", request.getRequestURI());
// 示例:验证Token
String token = request.getHeader("Authorization");
if (!isValidToken(token)) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid token");
return false;
}
return true;
}
/**
* 后处理回调方法(Controller方法执行后,视图渲染前)
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
logger.info(">>> postHandle: {}", request.getRequestURI());
// 可修改ModelAndView
if (modelAndView != null) {
modelAndView.addObject("interceptorMsg", "Processed by interceptor");
}
}
/**
* 整个请求完成后的回调方法(视图渲染完成后)
* 适合进行资源清理
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
logger.info(">>> afterCompletion: {}", request.getRequestURI());
// 记录请求耗时等
long startTime = (Long) request.getAttribute("startTime");
logger.info("Request '{}' completed in {}ms",
request.getRequestURI(),
System.currentTimeMillis() - startTime);
// 异常处理
if (ex != null) {
logger.error("Request processing failed", ex);
}
}
private boolean isValidToken(String token) {
// 实际项目中应实现真正的token验证逻辑
return token != null && token.startsWith("Bearer ");
}
}
2.2 注册拦截器
通过WebMvcConfigurer
配置拦截器的拦截规则:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private CustomInterceptor customInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor)
.addPathPatterns("/api/**") // 拦截路径
.excludePathPatterns("/api/public/**") // 排除路径
.order(1); // 拦截器执行顺序
// 可以注册多个拦截器
registry.addInterceptor(new LoggingInterceptor())
.addPathPatterns("/**")
.order(2);
}
}
三、拦截器高级应用
3.1 多拦截器执行顺序
当配置多个拦截器时,执行顺序如下:
- preHandle:按order值从小到大顺序执行
- Controller方法:所有preHandle返回true后执行
- postHandle:按order值从大到小逆序执行
- afterCompletion:按order值从大到小逆序执行
3.2 拦截器与异常处理
方案1:在afterCompletion中处理
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
if (ex instanceof BusinessException) {
response.resetBuffer();
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"" + ex.getMessage() + "\"}");
}
}
方案2:结合@ControllerAdvice
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ResponseEntity<?> handleBusinessException(BusinessException ex) {
return ResponseEntity.badRequest().body(ex.getMessage());
}
}
3.3 拦截器与异步请求
对于异步请求(Callable
/DeferredResult
),需要实现AsyncHandlerInterceptor
:
@Component
public class AsyncInterceptor implements AsyncHandlerInterceptor {
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 异步请求开始时调用(代替postHandle和afterCompletion)
System.out.println("Async request started");
}
}
四、实战应用场景
4.1 认证与授权拦截
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private AuthService authService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 检查是否需要登录
if (handlerMethod.hasMethodAnnotation(RequiresLogin.class)) {
String token = request.getHeader("X-Token");
if (!authService.validateToken(token)) {
throw new UnauthorizedException("请先登录");
}
}
// 检查权限
RequiresPermission permission = handlerMethod.getMethodAnnotation(RequiresPermission.class);
if (permission != null) {
String[] required = permission.value();
User user = authService.getCurrentUser();
if (!user.hasPermissions(required)) {
throw new ForbiddenException("权限不足");
}
}
return true;
}
}
4.2 请求日志记录
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
request.setAttribute("startTime", System.currentTimeMillis());
logRequest(request);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
long duration = System.currentTimeMillis() - (Long) request.getAttribute("startTime");
logResponse(request, response, duration, ex);
}
private void logRequest(HttpServletRequest request) {
String queryString = request.getQueryString();
String path = queryString == null ? request.getRequestURI() :
request.getRequestURI() + "?" + queryString;
Logger.info("Request [{} {}] from IP: {}",
request.getMethod(),
path,
request.getRemoteAddr());
}
private void logResponse(HttpServletRequest request,
HttpServletResponse response,
long duration,
Exception ex) {
Logger.info("Response [{} {}] status: {} duration: {}ms",
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration);
}
}
4.3 接口限流控制
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.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "请求过于频繁");
return false;
}
return true;
}
}
五、性能优化与最佳实践
5.1 拦截器优化技巧
- 轻量级preHandle:preHandle方法应尽可能高效,避免复杂逻辑
- 合理设置拦截路径:精确配置addPathPatterns,避免拦截不必要请求
- 异步处理耗时操作:如日志记录等操作可以放入线程池异步执行
- 使用@Order注解:明确指定拦截器顺序,避免依赖不可控的注册顺序
5.2 常见问题解决方案
问题1:拦截器不生效
检查步骤:
- 确认拦截器类有
@Component
注解 - 确认配置类有
@Configuration
且实现了WebMvcConfigurer
- 检查拦截路径(addPathPatterns)是否正确
- 查看是否有更高优先级的拦截器中断了请求
问题2:拦截器中注入Bean为null
解决方案:
- 确保拦截器本身是Spring管理的Bean(添加
@Component
) - 不要在new创建的拦截器实例中使用自动注入
- 通过构造器注入代替字段注入:
@Component
public class AuthInterceptor implements HandlerInterceptor {
private final AuthService authService;
@Autowired // 构造器注入
public AuthInterceptor(AuthService authService) {
this.authService = authService;
}
}
问题3:拦截静态资源
解决方案:
- 明确排除静态资源路径:
registry.addInterceptor(interceptor) .excludePathPatterns("/static/**", "/public/**");
- 或者精确配置拦截路径,避免
/**
这样的宽泛匹配
六、Spring Boot 3.x新特性
6.1 拦截器注册新方式
Spring Boot 3.x推荐使用@Configuration
结合@Bean
方式:
@Configuration
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor());
}
};
}
}
6.2 函数式编程支持
Spring 6.x引入了更简洁的函数式注册方式:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(request -> {
// preHandle逻辑
return true;
})
.addInterceptor((request, response, handler) -> {
// postHandle逻辑
})
.addInterceptor((request, response, handler, ex) -> {
// afterCompletion逻辑
});
}
}
七、总结与最佳实践
7.1 拦截器适用场景总结
场景 | 推荐实现方式 | 注意事项 |
---|---|---|
认证授权 | preHandle中进行校验 | 注意排除登录/公开接口 |
日志记录 | preHandle+afterCompletion组合 | 异步处理耗时日志操作 |
参数预处理 | preHandle中修改request参数 | 注意线程安全问题 |
接口限流 | preHandle中使用RateLimiter | 快速失败,避免阻塞线程 |
响应统一包装 | postHandle中修改ModelAndView | 仅适用于返回视图的场景 |
性能监控 | 记录请求开始和结束时间 | 使用System.nanoTime()更精确 |
7.2 项目实战建议
-
分层设计:
- 基础拦截器:处理日志、监控等通用逻辑
- 业务拦截器:处理认证、权限等业务逻辑
-
配置管理:
# application.yml interceptor: auth: enable: true exclude-paths: /public/**,/health log: enable: true
-
动态启用/禁用:
@ConditionalOnProperty(name = "interceptor.auth.enable", havingValue = "true") @Bean public AuthInterceptor authInterceptor() { return new AuthInterceptor(); }
-
测试策略:
- 单元测试:直接测试拦截器逻辑
- 集成测试:使用MockMvc测试拦截链
@Test void testAuthInterceptor() throws Exception { mockMvc.perform(get("/api/secured")) .andExpect(status().isUnauthorized()); }
拦截器作为Spring MVC的核心扩展点,合理使用可以极大提高代码的复用性和可维护性。掌握其原理和最佳实践,能够帮助开发者构建更加健壮和灵活的Web应用程序。