Spring MVC拦截器案例深度解析:从入门到实战(附完整代码)
目录导读
- 拦截器到底是什么?——核心概念与执行原理
- 第一个拦截器:5分钟快速上手(代码案例)
- 拦截器的“三兄弟”:preHandle / postHandle / afterCompletion 详解
- 多个拦截器的执行顺序与责任链模式
- 拦截器 vs 过滤器:别再傻傻分不清
- 企业级实战案例:登录鉴权 + 操作日志 + 性能监控
- 常见坑点与最佳实践(高频面试题问答)
拦截器到底是什么?——核心概念与执行原理
Spring MVC的拦截器(Interceptor) 是一种面向切面编程(AOP)的轻量级实现,它允许你在处理器(Controller)方法执行前后插入自定义逻辑,而无需修改原有代码,它的核心是基于HandlerExecutionChain链式调用,由DispatcherServlet统一调度。

执行原理流程图(简化版):
请求 → DispatcherServlet → 找到HandlerMapping → 返回HandlerExecutionChain
→ 依次执行所有拦截器的preHandle()
→ 若全部通过,则调用Controller方法
→ 依次执行postHandle()(逆序)
→ 视图渲染
→ 依次执行afterCompletion()(逆序)
与核心注解的关系:拦截器作用于HandlerMapping之后,因此它能看到请求对应的Handler(Controller方法),但看不到视图渲染之后的结果。
第一个拦截器:5分钟快速上手(代码案例)
Step 1:实现HandlerInterceptor接口
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("[First] 请求前执行,可以进行权限校验");
return true; // 放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[First] 请求后视图前执行,可以修改ModelAndView");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
System.out.println("[First] 视图渲染完成后执行,可以释放资源/记录日志");
}
}
Step 2:注册拦截器(Spring配置类)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FirstInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login", "/static/**"); // 放行登录页和静态资源
}
}
Step 3:测试 启动应用后访问任意Controller路径,控制台会按顺序打印三个方法中的LOG。
拦截器的“三兄弟”:三大核心方法深度对比
| 方法 | 执行时机 | 返回值 | 典型用途 |
|---|---|---|---|
| preHandle | Controller方法执行前 | boolean(true=放行,false=拦截) | 登录校验、权限检查、黑名单过滤 |
| postHandle | Controller方法执行后、视图渲染前 | void | 向ModelAndView注入公共数据、敏感词过滤 |
| afterCompletion | 视图渲染完成后(无论是否抛异常) | void | 日志记录、资源清理、性能统计 |
关键细节:
- 如果
preHandle返回false,后续该方法所在的整个链路(包括postHandle、afterCompletion和Controller方法)不会执行,但已执行的preHandle仍然会触发对应的afterCompletion(逆序)。 postHandle只有在该拦截器自身preHandle返回true后才会被调用。
多个拦截器的执行顺序与责任链模式
执行规则:多个拦截器按注册顺序形成一个责任链,执行顺序为:
请求 → Interceptor1.preHandle → Interceptor2.preHandle → Controller
→ Interceptor2.postHandle → Interceptor1.postHandle → 视图渲染
→ Interceptor2.afterCompletion → Interceptor1.afterCompletion
代码验证:
registry.addInterceptor(new InterceptorA()).addPathPatterns("/**");
registry.addInterceptor(new InterceptorB()).addPathPatterns("/**");
输出顺序:A.pre → B.pre → Controller → B.post → A.post → B.after → A.after
重要结论:
preHandle按注册顺序执行postHandle和afterCompletion按注册逆序执行(类似栈的弹出)
拦截器 vs 过滤器:别再傻傻分不清
| 对比维度 | Filter(过滤器) | Interceptor(拦截器) |
|---|---|---|
| 所属规范 | Servlet规范(服务端通用) | Spring框架自带(仅适用于Spring项目) |
| 作用范围 | 所有请求(包括静态资源) | 仅作用于HandlerMapping匹配的处理器 |
| 依赖容器 | 依赖Servlet容器(如Tomcat) | 依赖Spring IOC容器,可获取Bean |
| 访问能力 | 无法直接访问Spring管理的Bean | 可以直接注入Service、DAO |
| 执行时机 | 进入Servlet之前 | 进入DispatcherServlet之后 |
| 获取Handler | 只能拿到Request/Response | 可以获取目标Handler(Controller方法) |
| 典型场景 | 编码转换、CORS跨域、请求日志 | 权限校验、敏感词过滤、数据注入 |
适用选择:如果功能与Spring无关(如字符编码),用Filter;如果涉及Spring Bean或Controller信息,用Interceptor。
企业级实战案例:登录鉴权 + 操作日志 + 性能监控
案例场景
对/admin/**路径进行登录拦截,记录每个接口调用耗时,并写入操作日志。
完整代码:
@Component
public class AdminAuthInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService; // 注入业务服务
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 1. 判断是否登录(从Session或Token获取)
Object user = request.getSession().getAttribute("LOGIN_USER");
if (user == null) {
// 未登录,重定向到登录页
response.sendRedirect("/login");
return false;
}
// 2. 判断路径权限(假设:所有/admin/delete需要管理员权限)
String uri = request.getRequestURI();
if (uri.contains("/delete") && !userService.isAdmin((User) user)) {
response.sendError(403, "无权限操作");
return false;
}
// 3. 将用户信息放入请求域,供Controller使用
request.setAttribute("currentUser", user);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// 向所有Admin页面注入公共数据(如菜单列表)
if (modelAndView != null) {
modelAndView.addObject("menu", menuService.getAdminMenus());
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
// 记录访问日志(包括耗时)
long startTime = (Long) request.getAttribute("startTime");
long costTime = System.currentTimeMillis() - startTime;
logService.writeLog(request.getRequestURI(), costTime, request.getRemoteAddr());
// 资源清理
System.out.println("接口耗时:" + costTime + "ms");
}
}
注册配置增强:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new BaseInterceptor()) // 公共拦截:获取起始时间
.addPathPatterns("/**");
registry.addInterceptor(adminAuthInterceptor) // 管理端鉴权
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/login", "/admin/logout");
}
常见坑点与最佳实践(高频面试题问答)
Q1:preHandle返回false后,afterCompletion还会执行吗?
分为两种情况:如果当前拦截器的preHandle返回false,则当前拦截器的afterCompletion不会执行;但之前已执行过preHandle并返回true的拦截器的afterCompletion会逆序执行,例:A.pre=true → B.pre=false → 只会执行A.after。
Q2:拦截器如何获取Controller方法的参数?
通过
HandlerMethod对象:HandlerMethod hm = (HandlerMethod) handler;然后使用hm.getMethodParameters()获取参数列表,或hm.getMethod()获取注解。
Q3:拦截器能拦截异步请求(DeferredResult)吗?
默认情况下,拦截器只拦截第一次请求,对于异步处理,Spring 3.2+提供了
AsyncHandlerInterceptor接口,其中afterConcurrentHandlingStarted方法在异步请求开始时触发。
Q4:如何让拦截器不拦截静态资源?
在注册时使用
excludePathPatterns("/static/**", "/css/**", "/js/**", "/images/**"),或者更优雅的方式是使用resourceHandler配置。
最佳实践总结:
- 善用排除路径:登录页、静态资源、开放API一定不要拦截
- 获取Bean:拦截器要注入Service时,建议使用
@Component注解而不是new,或在配置中手动注入 - 异常处理:拦截器中的异常如果未捕获,会中断请求处理,建议在
afterCompletion中统一处理 - 性能考虑:拦截器调用的外部服务要加超时控制,避免阻塞
- 测试策略:单元测试时注意mock拦截器,集成测试时通过MockMvc验证拦截行为
Spring MVC拦截器是构建健壮Web应用的利器,它让你在不侵入业务代码的前提下,优雅地实现横切关注点,从简单的日志打印到复杂的权限体系,掌握好这"三兄弟"加上责任链规则,足以应对绝大多数企业级需求,建议你亲手部署一个含有登录鉴权+性能监控的Demo,彻底吃透它的生命周期。