拦截器
Spring MVC 拦截器详解:从原理到实践
拦截器(Interceptor)是 Spring MVC 提供的核心功能之一,用于在请求处理的不同阶段进行拦截并执行自定义逻辑(如权限校验、日志记录、性能监控等)。它与 Servlet 过滤器(Filter)类似,但更贴合 Spring MVC 的生命周期,使用更灵活。从 “拦截器与过滤器的区别→拦截器的使用→执行原理→实战场景” 全面解析 Spring MVC 拦截器。
拦截器与过滤器的核心区别
虽然拦截器和过滤器都能实现请求拦截,但两者在技术归属、执行时机、拦截范围等方面有本质区别:
| 特性 | 拦截器(Interceptor) | 过滤器(Filter) |
|---|---|---|
| 技术归属 | Spring MVC 框架提供 | Servlet 规范提供(Java EE 原生) |
| 执行时机 | 在 DispatcherServlet 内部执行 | 在 DispatcherServlet 之前执行 |
| 拦截范围 | 仅拦截 Spring MVC 管理的请求(如 @RequestMapping 标注的方法) |
拦截所有进入 Web 容器的请求(包括静态资源、JSP 等) |
| 底层实现 | 基于 Java 反射机制 | 基于函数回调 |
| 生命周期管理 | 由 Spring 容器管理,可注入 Spring Bean | 由 Web 容器管理,无法直接使用 Spring 资源 |
| 方法粒度 | 可精确到 Controller 方法级别 | 仅能到 URL 级别 |
拦截器的核心接口与方法
Spring MVC 拦截器通过 HandlerInterceptor 接口定义核心行为,包含三个关键方法,分别对应请求处理的不同阶段:
public interface HandlerInterceptor {
/**
* 1. 预处理:目标 Controller 方法执行前调用
* @param request 请求对象
* @param response 响应对象
* @param handler 目标处理器(通常是 Controller 方法)
* @return true:继续执行(后续拦截器或目标方法);false:中断执行(直接返回响应)
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/**
* 2. 后处理:目标 Controller 方法执行后、视图渲染前调用
* @param modelAndView 目标方法返回的 ModelAndView 对象(可修改视图或模型数据)
*/
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
/**
* 3. 完成处理:视图渲染后调用(无论是否发生异常)
* @param ex 目标方法执行过程中抛出的异常(无异常则为 null)
*/
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
}
方法作用与典型场景
| 方法名 | 执行阶段 | 典型应用场景 |
|---|---|---|
preHandle |
目标方法执行前 | 权限校验(如未登录则拦截)、日志记录(请求开始时间)、参数校验 |
postHandle |
目标方法执行后,视图渲染前 | 修改模型数据(如统一添加全局参数)、替换视图 |
afterCompletion |
视图渲染后 | 资源释放(如关闭文件流)、日志记录(请求结束时间,计算耗时) |
拦截器的使用步骤
使用拦截器需经过 “实现接口→配置拦截规则” 两步,以下是详细流程:
步骤 1:实现拦截器接口
方式 1:直接实现 HandlerInterceptor 接口(推荐)
需重写所有三个方法(无需实现的方法可留空):
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
// 预处理:检查用户是否登录
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 从 Session 中获取用户信息
Object user = request.getSession().getAttribute("loginUser");
// 2. 未登录:重定向到登录页,返回 false 中断执行
if (user == null) {
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
// 3. 已登录:继续执行后续流程
return true;
}
// 后处理:可修改视图或模型(本例无需实现)
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 示例:统一添加当前时间到模型
if (modelAndView != null) {
modelAndView.addObject("currentTime", System.currentTimeMillis());
}
}
// 完成处理:释放资源(本例无需实现)
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 示例:记录请求处理耗时
Long startTime = (Long) request.getAttribute("startTime");
if (startTime != null) {
long cost = System.currentTimeMillis() - startTime;
System.out.println("请求 " + request.getRequestURI() + " 耗时:" + cost + "ms");
}
}
}
方式 2:继承 HandlerInterceptorAdapter(已过时,不推荐)
Spring 5.3 后该类已标记为过时,建议直接实现接口:
// 过时写法,不推荐使用
public class OldInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 仅实现需要的方法
return true;
}
}
步骤 2:配置拦截器(XML 或 Java 配置)
方式 1:XML 配置(传统项目)
通过 <mvc:interceptors> 标签配置拦截器及拦截规则:
<!-- Spring MVC 配置文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 1. 全局拦截器:拦截所有请求(不推荐,通常需要排除静态资源) -->
<!-- <bean class="com.example.interceptor.GlobalInterceptor"/> -->
<!-- 2. 带拦截规则的拦截器(推荐) -->
<mvc:interceptor>
<!-- 拦截路径:/** 表示所有请求(包括子路径) -->
<mvc:mapping path="/**"/>
<!-- 排除路径:不拦截登录页和静态资源 -->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/static/**"/>
<!-- 拦截器实现类 -->
<bean class="com.example.interceptor.LoginInterceptor"/>
</mvc:interceptor>
<!-- 可配置多个拦截器,按配置顺序执行 preHandle 方法 -->
<mvc:interceptor>
<mvc:mapping path="/admin/**"/> <!-- 仅拦截 admin 路径下的请求 -->
<bean class="com.example.interceptor.AdminInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
方式 2:Java 配置(现代项目,推荐)
通过 WebMvcConfigurer 的 addInterceptors 方法配置:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// 标记为配置类
public class WebMvcConfig implements WebMvcConfigurer {
// 注册拦截器
public void addInterceptors(InterceptorRegistry registry) {
// 1. 注册登录拦截器
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login", "/static/**"); // 排除路径
// 2. 注册管理员拦截器(仅拦截 /admin 路径)
registry.addInterceptor(new AdminInterceptor())
.addPathPatterns("/admin/**");
// 注意:多个拦截器的注册顺序即 preHandle 执行顺序
}
}
拦截器的执行原理(结合源码)
拦截器的执行由 DispatcherServlet 主导,核心逻辑在 doDispatch 方法中。当请求到达 DispatcherServlet 后,会先通过 HandlerMapping 获取 HandlerExecutionChain(包含目标处理器和拦截器列表),再按顺序调用拦截器方法。
1. preHandle 方法:目标方法执行前调用(正序执行)
DispatcherServlet 在调用目标方法前,通过 HandlerExecutionChain 的 applyPreHandle 方法执行所有拦截器的 preHandle:
// DispatcherServlet 核心方法片段
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
try {
// ... 省略其他逻辑 ...
// 1. 获取 HandlerExecutionChain(包含拦截器)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. 执行所有拦截器的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return; // 若有拦截器返回 false,直接结束
}
// 3. 执行目标 Controller 方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// ... 后续逻辑 ...
}
}
// HandlerExecutionChain 的 applyPreHandle 方法
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 正序遍历拦截器,执行 preHandle
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
// 若当前拦截器返回 false,触发已执行拦截器的 afterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
// 记录最后一个成功执行 preHandle 的拦截器索引
this.interceptorIndex = i;
}
}
return true;
}
关键逻辑:
- 拦截器按配置顺序(正序)执行
preHandle; - 若某个拦截器返回
false,则触发 “已成功执行preHandle的拦截器” 的afterCompletion方法(反序),并终止后续流程。
2. postHandle 方法:目标方法执行后调用(反序执行)
目标方法执行完成后,DispatcherServlet 调用 HandlerExecutionChain 的 applyPostHandle 方法执行 postHandle:
// DispatcherServlet 核心方法片段
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ... 省略 preHandle 和目标方法执行逻辑 ...
// 执行所有拦截器的 postHandle 方法
if (mv != null && !mv.wasCleared()) {
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
// HandlerExecutionChain 的 applyPostHandle 方法
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 反序遍历拦截器,执行 postHandle
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
关键逻辑:
- 拦截器按配置的反序执行
postHandle; - 仅当所有拦截器的
preHandle都返回true时,才会执行postHandle。
3. afterCompletion 方法:视图渲染后调用(反序执行)
无论请求处理是否发生异常,afterCompletion 都会在视图渲染后执行:
// DispatcherServlet 核心方法片段
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
try {
// ... 省略其他逻辑 ...
} catch (Exception ex) {
// 发生异常时,触发 afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} finally {
// 正常结束时,触发 afterCompletion
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(processedRequest, response, null);
}
}
}
// HandlerExecutionChain 的 triggerAfterCompletion 方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 从最后一个成功执行 preHandle 的拦截器开始,反序执行 afterCompletion
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable ex2) {
logger.error("拦截器 afterCompletion 执行异常", ex2);
}
}
}
}
关键逻辑:
- 仅执行 “
preHandle成功返回true” 的拦截器的afterCompletion; - 按
preHandle执行顺序的反序执行(与postHandle顺序一致)。
多个拦截器的执行顺序
当配置多个拦截器时,执行顺序遵循 “preHandle 正序,postHandle/afterCompletion 反序” 的规则。
示例:两个拦截器的执行流程
假设配置两个拦截器 InterceptorA 和 InterceptorB(A 在前,B 在后):
// 配置顺序:InterceptorA → InterceptorB
registry.addInterceptor(new InterceptorA()).addPathPatterns("/**");
registry.addInterceptor(new InterceptorB()).addPathPatterns("/**");
执行顺序如下:
1. InterceptorA.preHandle() → 返回 true
2. InterceptorB.preHandle() → 返回 true
3. 执行目标 Controller 方法
4. InterceptorB.postHandle()
5. InterceptorA.postHandle()
6. 视图渲染
7. InterceptorB.afterCompletion()
8. InterceptorA.afterCompletion()
特殊情况:若 InterceptorB.preHandle() 返回 false:
1. InterceptorA.preHandle() → 返回 true
2. InterceptorB.preHandle() → 返回 false
3. InterceptorA.afterCompletion() → 仅执行已成功 preHandle 的拦截器
异步请求中的拦截器
对于异步请求(如返回 Callable、DeferredResult),Spring MVC 提供 AsyncHandlerInterceptor 接口(继承 HandlerInterceptor),新增 afterConcurrentHandlingStarted 方法:
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
/**
* 异步请求开始时调用(此时主线程已释放)
* 注意:异步请求中不会执行 postHandle 和 afterCompletion
*/
default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
}
}
执行逻辑:
- 异步请求处理时,
preHandle正常执行; - 主线程会跳过
postHandle和afterCompletion,直接调用afterConcurrentHandlingStarted; - 异步任务完成后,若需要后续处理,需通过
AsyncWebRequest注册回调。
拦截器的典型应用场景
- 权限校验:在
preHandle中检查用户是否登录或拥有操作权限(如示例中的LoginInterceptor); - 日志记录:在
preHandle记录请求开始时间,在afterCompletion记录结束时间,计算接口耗时; - 参数统一处理:在
preHandle中对请求参数进行加密 / 解密、XSS 过滤等; - 全局数据添加:在
postHandle中向ModelAndView添加全局参数(如当前登录用户信息、系统配置); - 资源释放:在
afterCompletion中关闭文件流、释放数据库连接等。
总结
Spring MVC 拦截器是基于 HandlerInterceptor 接口的请求拦截机制,核心特点如下:
- 三个核心方法:
preHandle(前置处理)、postHandle(后置处理)、afterCompletion(完成处理),分别对应请求处理的不同阶段; - 执行顺序:多个拦截器时,
preHandle正序执行,postHandle和afterCompletion反序执行; - 与过滤器的区别:拦截器属于 Spring MVC,在
DispatcherServlet内部执行,可精确到方法级别,且能使用 Spring 资源; - 灵活配置:支持 XML 和 Java 两种配置方式,可通过
addPathPatterns和excludePathPatterns精确控制拦截范围