1. 引言:拦截器在Web开发中的重要性
在现代Web应用开发中,我们经常需要在请求处理的前后执行一些通用逻辑,例如身份验证、日志记录、性能监控、权限检查等。如果将这些逻辑分散到每个控制器方法中,会导致代码重复、维护困难以及关注点混淆。
Spring拦截器(Interceptor)正是为了解决这一问题而设计的核心组件。它基于AOP(面向切面编程) 思想,允许你在请求到达控制器之前和之后插入自定义逻辑,实现对HTTP请求的精细化控制。
比喻:如果将请求处理比作一场演出,那么拦截器就像是后台的舞台管理人员。他们在演员(控制器)上台前检查服装和道具(预处理),在演出结束后清理舞台和记录表现(后处理),甚至决定是否允许演员登台(拦截)。
2. 拦截器 vs 过滤器:明确边界与职责
在深入拦截器之前,必须明确它与Servlet过滤器的区别和关系。以下是两者的对比:
特性 |
拦截器 (Interceptor) |
过滤器 (Filter) |
所属规范 |
Spring框架特有 |
Servlet规范标准 |
依赖关系 |
依赖于Spring容器 |
不依赖Spring,属于Web容器 |
作用范围 |
只能拦截Controller请求 |
可以拦截所有HTTP请求(静态资源等) |
访问上下文 |
可以访问Spring的Bean和上下文 |
只能访问Servlet API |
实现复杂度 |
简单,提供更精细的控制点 |
相对底层,控制粒度较粗 |
两者在请求处理流程中的位置关系如下图所示:
从图中可以看出,过滤器是Web应用的第一道防线,而拦截器是Spring MVC框架内部的处理机制。
3. 拦截器核心接口与执行流程
3.1 HandlerInterceptor接口详解
Spring拦截器的核心是HandlerInterceptor接口,它定义了三个方法:
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 { } }
3.2 拦截器执行流程深度解析
为了更直观地理解拦截器的执行流程,我们通过一个序列图来展示:
关键点说明:
- preHandle()方法按拦截器配置顺序执行,返回false时中断流程
- postHandle()方法按拦截器配置的逆序执行
- afterCompletion()方法同样按配置的逆序执行,无论控制器是否抛出异常都会执行
4. 实战演练:自定义拦截器开发
4.1 创建自定义拦截器
让我们通过几个实际案例来演示如何创建和使用拦截器。
示例1:日志记录拦截器
@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("请求开始: URL={}, Method={}, IP={}", request.getRequestURL(), request.getMethod(), request.getRemoteAddr()); return true; // 继续执行流程 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { long startTime = (Long) request.getAttribute("startTime"); long endTime = System.currentTimeMillis(); long executeTime = endTime - startTime; logger.info("请求处理完成: URL={}, 执行时间={}ms", request.getRequestURL(), executeTime); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { if (ex != null) { logger.error("请求处理异常: URL={}, 异常信息={}", request.getRequestURL(), ex.getMessage()); } else { logger.info("请求完全结束: URL={}", request.getRequestURL()); } } }
示例2:身份验证拦截器
@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 检查是否是登录相关请求,避免循环拦截 if (request.getRequestURI().contains("/login")) { return true; } // 检查会话中是否有用户信息 HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user") == null) { // 未登录,重定向到登录页面 response.sendRedirect(request.getContextPath() + "/login"); return false; // 中断请求 } return true; // 继续执行 } }
4.2 注册与配置拦截器
创建拦截器后,需要通过配置类将其注册到Spring MVC中:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Autowired private LoggingInterceptor loggingInterceptor; @Autowired private AuthInterceptor authInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { // 注册日志拦截器,拦截所有请求 registry.addInterceptor(loggingInterceptor) .addPathPatterns("/**") // 拦截所有路径 .excludePathPatterns("/static/**"); // 排除静态资源 // 注册认证拦截器,拦截特定路径 registry.addInterceptor(authInterceptor) .addPathPatterns("/admin/**", "/user/**") // 拦截管理端和用户端 .excludePathPatterns("/login", "/register", "/error"); // 排除登录注册页 // 可以设置拦截器执行顺序 registry.addInterceptor(new AnotherInterceptor()) .addPathPatterns("/**") .order(1); // 数字越小优先级越高 } }
4.3 测试拦截器效果
编写测试控制器来验证拦截器工作:
@RestController @RequestMapping("/api") public class TestController { @GetMapping("/public/data") public ResponseEntity<String> publicData() { return ResponseEntity.ok("这是公开数据"); } @GetMapping("/private/data") public ResponseEntity<String> privateData(HttpSession session) { String user = (String) session.getAttribute("user"); return ResponseEntity.ok("这是私有数据,用户: " + user); } @PostMapping("/login") public ResponseEntity<String> login(@RequestParam String username, HttpSession session) { session.setAttribute("user", username); return ResponseEntity.ok("登录成功"); } }
使用curl或Postman测试:
# 测试公开接口(应正常返回) curl http://localhost:8080/api/public/data # 测试私有接口(应被重定向) curl http://localhost:8080/api/private/data # 登录后再测试私有接口 curl -X POST -d "username=testuser" http://localhost:8080/api/login curl http://localhost:8080/api/private/data
5. 高级应用与最佳实践
5.1 多拦截器执行顺序控制
当有多个拦截器时,执行顺序非常重要:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptorA).order(1); // 最先执行preHandle,最后执行postHandle/afterCompletion registry.addInterceptor(interceptorB).order(2); registry.addInterceptor(interceptorC).order(3); // 最后执行preHandle,最先执行postHandle/afterCompletion }
5.2 基于注解的精细化控制
你可以创建自定义注解来实现更精细的拦截控制:
// 定义权限注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequiredPermission { String[] value() default {}; } // 在拦截器中检查注解 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; RequiredPermission permission = handlerMethod.getMethodAnnotation(RequiredPermission.class); if (permission != null) { // 检查用户是否有所需权限 String[] requiredPermissions = permission.value(); if (!hasPermissions(request, requiredPermissions)) { response.sendError(HttpStatus.FORBIDDEN.value(), "权限不足"); return false; } } } return true; }
5.3 异常处理与统一响应
在拦截器中实现统一异常处理:
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { if (ex != null) { // 记录异常日志 logger.error("请求处理异常", ex); // 统一异常响应 if (!response.isCommitted()) { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); Map<String, Object> errorResponse = new HashMap<>(); errorResponse.put("code", 500); errorResponse.put("message", "系统内部错误"); errorResponse.put("timestamp", System.currentTimeMillis()); response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse)); } } }
6. 常见问题与解决方案
6.1 拦截器不生效的可能原因
- 配置类未加载:确保配置类被@ComponentScan扫描到
- 路径模式错误:检查addPathPatterns和excludePathPatterns配置
- 静态资源被拦截:确保排除静态资源路径
- 拦截器顺序问题:某些拦截器中断了请求链
6.2 性能优化建议
- 避免在preHandle中执行耗时操作:如数据库查询等
- 使用缓存:对重复数据进行缓存,如权限信息
- 合理设置拦截范围:不要过度使用拦截器,避免不必要的拦截
7. 总结
Spring拦截器是Web开发中极其重要的组件,它提供了以下核心价值:
- 代码复用:将横切关注点(如日志、认证、授权)从业务逻辑中分离
- 请求预处理:在控制器执行前进行验证、准备数据等操作
- 响应后处理:在控制器执行后统一处理响应数据
- 流程控制:可以中断请求处理流程,实现权限控制等功能
通过合理使用拦截器,你可以构建出更加健壮、可维护和安全的Web应用程序。掌握拦截器的原理和使用技巧,是每个Spring开发者必备的核心能力。
最佳实践提示:
保持拦截器职责单一,每个拦截器只做一件事
谨慎使用afterCompletion,注意性能影响
对于复杂业务逻辑,考虑使用Spring AOP代替拦截器
始终考虑异常处理情况,确保资源正确释放