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代替拦截器
始终考虑异常处理情况,确保资源正确释放
1330

被折叠的 条评论
为什么被折叠?



