
1. Spring MVC拦截器核心原理剖析当面试官抛出说说Spring MVC拦截器的原理这个问题时他实际上在考察你对Spring框架请求处理流程的底层认知。作为Spring MVC的核心扩展点之一拦截器Interceptor的设计体现了典型的AOP思想也是实际项目中实现权限控制、日志记录等横切关注点的首选方案。我在多个电商和金融项目中深度使用过拦截器机制发现很多开发者虽然会配置HandlerInterceptor接口但对拦截器与过滤器(Filter)的本质区别、拦截器的执行时机等底层细节理解模糊。本文将结合Spring 5.3源码拆解拦截器从注册到执行的全流程并分享我在高并发场景下优化拦截器性能的实战经验。2. 拦截器与过滤器的本质区别2.1 架构层级对比过滤器是Servlet规范定义的组件其作用在Servlet容器层面。通过web.xml或WebFilter配置的过滤器会对所有进入容器的请求进行预处理。而拦截器是Spring MVC框架层面的概念只能拦截进入DispatcherServlet的请求。我曾在一个迁移项目中遇到经典案例当应用同时使用Shiro基于Filter和Spring Security基于Interceptor时Shiro的过滤器链会先执行。这种执行顺序差异直接影响了认证流程的设计。2.2 功能特性差异过滤器可以处理所有内容包括静态资源但无法获取Spring上下文信息。拦截器虽然作用范围有限但能直接使用Spring的依赖注入和AOP特性。下表是核心功能对比特性过滤器(Filter)拦截器(Interceptor)所属规范Servlet APISpring MVC执行时机前置/后置处理更细粒度的三个阶段访问Spring Bean不可直接访问可直接注入异常处理只能捕获Servlet异常可捕获Controller异常性能影响容器级开销较大框架级开销较小3. 拦截器核心接口解析3.1 HandlerInterceptor三阶段设计Spring设计了三个关键方法形成拦截链public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } 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 { } }preHandle在HandlerAdapter调用Controller方法前执行返回false会中断流程。我在权限校验时常用此方法注意要在此处做好线程安全的参数解析。postHandleController执行完毕但视图渲染前触发。这里可以修改ModelAndView但要注意并发修改问题。一个实际踩坑案例在此处添加全局属性时未考虑视图技术差异导致Thymeleaf模板报错。afterCompletion整个请求完成后回调适合资源清理。统计接口耗时建议在此实现但要注意异常情况下的性能采样准确性。3.2 AsyncHandlerInterceptor扩展对于异步请求Spring提供了增强接口public interface AsyncHandlerInterceptor extends HandlerInterceptor { default void afterConcurrentHandlingStarted( HttpServletRequest request, HttpServletResponse response, Object handler) { } }当Controller返回Callable或DeferredResult时afterConcurrentHandlingStarted会立即执行而常规的postHandle和afterCompletion会在异步处理完成后触发。在文件导出等异步场景中这个特性可以用来及时释放线程资源。4. 拦截器注册与执行流程4.1 拦截器注册机制在Spring Boot中配置拦截器有两种主流方式实现WebMvcConfigurer推荐Configuration public class AuthConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/public/**); } }声明InterceptorRegistrationBeanBean public InterceptorRegistrationBeanLogInterceptor logInterceptor() { InterceptorRegistrationBeanLogInterceptor bean new InterceptorRegistrationBean(new LogInterceptor()); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; }重要提示拦截器默认按注册顺序执行通过order属性可调整顺序。在微服务网关项目中我曾因顺序错误导致认证拦截器在日志拦截器之后执行引发严重安全漏洞。4.2 拦截器执行源码追踪核心流程发生在DispatcherServlet#doDispatch方法中获取拦截器链HandlerExecutionChain mappedHandler getHandler(processedRequest); if (mappedHandler null) { noHandlerFound(processedRequest, response); return; }按顺序执行preHandleif (!mappedHandler.applyPreHandle(processedRequest, response)) { return; // 任一拦截器返回false则中断 }实际处理请求Controller方法调用逆序执行postHandlemappedHandler.applyPostHandle(processedRequest, response, mv);最终触发afterCompletionprocessDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);在压测中发现拦截器链的构建存在优化空间。对于静态资源路径可以通过excludePathPatterns提前过滤避免不必要的拦截器初始化。5. 实战中的性能优化技巧5.1 拦截器短路设计对于耗时的权限校验可以采用分级拦截策略public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 快速路径放行公开接口 if (isPublicAPI(request)) { return true; } // 基础校验Token格式等 if (!basicCheck(request)) { sendError(response, 401); return false; } // 深度校验数据库查询等 return deepAuthCheck(request); }这种模式在我的一个秒杀项目中将平均拦截时间从15ms降到了3ms。5.2 线程局部变量管理在拦截器中存储用户上下文时必须注意线程安全问题public class UserContextInterceptor implements HandlerInterceptor { private static final ThreadLocalUserInfo userHolder new ThreadLocal(); Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { UserInfo user parseUser(request); userHolder.set(user); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { userHolder.remove(); // 必须清理防止内存泄漏 } }特别是在使用Tomcat线程池时忘记清理ThreadLocal会导致用户信息错乱。建议配合ControllerAdvice实现统一清理。5.3 拦截器与异常处理器的协作当拦截器抛出异常时常规的ExceptionHandler无法捕获。解决方案是在preHandle中捕获业务异常转换为响应try { authService.checkPermission(request); } catch (BusinessException e) { response.setStatus(403); response.getWriter().write(e.getMessage()); return false; }或者注册专门的HandlerExceptionResolverBean public HandlerExceptionResolver customExceptionResolver() { return (request, response, handler, ex) - { if (ex instanceof AuthException) { response.sendError(403, ex.getMessage()); return new ModelAndView(); } return null; }; }6. 典型应用场景实现6.1 接口耗时监控结合Slf4j的MDC实现全链路监控public class MetricsInterceptor implements HandlerInterceptor { private static final String START_TIME startTime; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { long start System.currentTimeMillis(); request.setAttribute(START_TIME, start); MDC.put(traceId, UUID.randomUUID().toString()); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { long duration System.currentTimeMillis() - (long)request.getAttribute(START_TIME); log.info({} {} cost {}ms, request.getMethod(), request.getRequestURI(), duration); MDC.clear(); } }6.2 重复提交防御基于Redis实现幂等性控制public class IdempotentInterceptor implements HandlerInterceptor { Autowired private RedisTemplateString, String redisTemplate; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(X-Idempotent-Token); if (StringUtils.isEmpty(token)) { response.setStatus(400); return false; } String key idempotent: token; Boolean success redisTemplate.opsForValue() .setIfAbsent(key, 1, 5, TimeUnit.MINUTES); if (Boolean.FALSE.equals(success)) { response.setStatus(409); // Conflict return false; } return true; } }在支付系统中这种设计可以有效防止因网络重试导致的重复扣款。6.3 参数预处理统一处理加密参数public class DecryptInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (handler instanceof HandlerMethod) { String encrypted request.getParameter(data); String decrypted AESUtil.decrypt(encrypted); request.setAttribute(decryptedParams, JSON.parseObject(decrypted)); } return true; } }配合自定义参数解析器可以实现无缝的参数注入GetMapping(/detail) public Result? getDetail(DecryptedParam OrderQuery query) { // 直接使用解密后的对象 }7. 高级特性与常见陷阱7.1 拦截器与CORS的冲突当同时使用拦截器和Spring的CrossOrigin注解时可能会遇到预检请求(OPTIONS)被拦截的问题。解决方案是Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .excludePathPatterns(HttpMethod.OPTIONS.toString()); }或者在拦截器中特殊处理OPTIONS方法public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (HttpMethod.OPTIONS.matches(request.getMethod())) { return true; } // 正常处理逻辑 }7.2 文件上传的特殊处理在文件上传场景中拦截器的postHandle执行时文件可能还未完全写入磁盘。此时操作文件可能导致资源锁定建议在preHandle中验证文件大小/类型在afterCompletion中进行后续处理public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { MultipartFile file (MultipartFile)request.getAttribute(file); if (file ! null ex null) { asyncService.processUploadFile(file); } }7.3 拦截器与响应包装当需要统一包装响应时直接修改响应内容可能导致乱码public class ResponseWrapperInterceptor implements HandlerInterceptor { Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) { if (mv ! null) { Object data mv.getModel().get(data); mv.getModel().clear(); mv.addObject(success, true); mv.addObject(data, data); } } }更推荐使用ResponseBodyAdvice实现响应包装避免与视图渲染冲突。