Spring MVC 拦截器
Spring MVC 拦截器
拦截器是 Spring MVC 中强大的控件,它可以在进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作。Spring MVC 会在启动期间就通过 @RequestMapping 的注解解析 URI 和处理器的对应关系,在运行的时候通过请求找到对应的 HandlerMapping,然后构建 HandlerExecutionChain 对象,它是一个执行的责任链对象。
对于拦截器所需要关注的有两点:
- 它有哪些方法,方法的含义是什么;
- 各个方法在流程中执行的顺序是如何。
拦截器的定义
Spring 要求处理器的拦截器都要实现 org.springframework.web.servlet.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,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
三个方法的意义
- preHandler 方法:在处理器之前执行的前置方法,Spring MVC 可在进入处理器前通过该方法处理一些逻辑。它返回一个 boolean 值,会影响后续流程(返回 true 则继续执行,返回 false 则终止后续流程)。
- postHandler 方法:在处理器之后执行的后置方法,处理器的核心逻辑完成后运行。
- afterCompletion 方法:无论是否产生异常,都会在渲染视图后执行的方法,常用于资源清理等操作。
说明:在新版本中,这三个方法被定义为默认方法。因为实际开发中可能只需要实现其中某一个回调方法,老版本中为了简化这种场景,Spring 提供了
HandlerInterceptorAdapter适配器(适配器设计模式的实现),供开发者选择性继承。
拦截器的执行流程
拦截器可能配置多个,先介绍单个拦截器的执行流程,后续再说明多个拦截器的场景:
关键说明:
- 可在处理器执行前后、视图渲染后插入自定义逻辑;
- 当前置方法(preHandle)返回 false 时,后续的处理器逻辑、后置方法(postHandle)、afterCompletion 方法都不会执行。
开发拦截器
下面开发一个简单的角色拦截器(测试用),可通过实现 HandlerInterceptor 接口。
角色拦截器代码
package com.intercreptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class RoleInterceptor implements HandlerInterceptor {
/**
* 前置方法
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器对象
* @return true 表示放行,false 表示不放行
* @throws Exception 可能抛出的异常
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("RoleInterceptor 前置方法, handler:" + ((HandlerMethod) handler).getMethod().toString());
return true; // 返回 true 放行,返回 false 终止后续流程
}
/**
* 后置方法
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器对象
* @param modelAndView 模型和视图对象(可修改视图数据)
* @throws Exception 可能抛出的异常
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("RoleInterceptor 后置方法, handler:" + ((HandlerMethod) handler).getMethod().toString());
// 遍历模型中的数据
modelAndView.getModel().forEach((k, v) -> System.out.println("key:" + k + ";value:" + v));
// 获取逻辑视图名称
System.out.println("获取逻辑视图名称:" + modelAndView.getViewName());
}
/**
* 渲染完成方法(无论是否异常都会执行)
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器对象
* @param ex 执行过程中抛出的异常(无异常则为 null)
* @throws Exception 可能抛出的异常
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("RoleInterceptor 渲染完成方法(无论异常与否), handler:" + ((HandlerMethod) handler).getMethod().toString());
System.out.println("异常:" + ex);
}
}
配置拦截器(Java 配置)
通过实现 WebMvcConfigurer 接口的 addInterceptors 方法配置拦截器,同时可配置静态资源、跨域、视图解析器等相关参数:
package com.config;
import com.converter.String2Position;
import com.intercreptor.RoleInterceptor;
import org.hibernate.validator.HibernateValidator;
import org.jspecify.annotations.Nullable;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.format.FormatterRegistry;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* Spring 为了方便 Spring MVC 相关配置,提供了 WebMvcConfigurer 接口
*/
@Configuration
@ComponentScan(basePackages = "com.controller") // 扫描控制器所在包
@EnableWebMvc // 以注解方式驱动 Spring MVC(包含注解映射的 HandlerMapping、HandlerAdapter 等组件)
public class ServletConfig implements WebMvcConfigurer {
private final MessageSource messageSource;
public ServletConfig(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* 配置静态资源不被拦截(css、js、图片、视频等)
* @param registry 资源处理器注册器
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**") // 静态资源的访问路径(根路径下所有请求)
.addResourceLocations("/public/"); // 静态资源实际存放目录(访问时无需拼接 public 路径)
}
/**
* 配置全局跨域(局部跨域可使用 @CrossOrigin 注解)
* @param registry 跨域注册器
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许所有请求跨域
.allowedOrigins("http://localhost:5173") // 允许的跨域来源(需携带 Cookie 时,不可设为 *)
.allowedHeaders("*") // 允许所有请求头
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD") // 允许的请求方式
.allowCredentials(true) // 允许携带 Cookie
.maxAge(3600); // 跨域缓存有效时间(1小时)
}
/**
* 配置视图解析器
* @param registry 视图解析器注册器
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(
new InternalResourceViewResolver("/WEB-INF/pages/", ".jsp") // 视图前缀和后缀
);
}
/**
* 配置拦截器
* @param registry 拦截器注册器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RoleInterceptor()) // 注册自定义拦截器
.addPathPatterns("/staff/**"); // 指定拦截的请求路径(/staff/ 下所有请求,默认拦截所有)
}
}
多个拦截器的执行顺序
当配置多个拦截器时,执行顺序遵循「责任链模式」,具体分为两种情况:所有前置方法返回 true、某一个前置方法返回 false。
准备多个拦截器
创建三个简单的拦截器,仅打印执行日志:
拦截器 1
public class RoleInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle1");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle1");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion1");
}
}
拦截器 2
public class RoleInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle2");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle2");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion2");
}
}
拦截器 3
public class RoleInterceptor3 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle3");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle3");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion3");
}
}
配置多个拦截器
在 ServletConfig 的 addInterceptors 方法中按顺序注册三个拦截器:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RoleInterceptor1()).addPathPatterns("/control/**");
registry.addInterceptor(new RoleInterceptor2()).addPathPatterns("/control/**");
registry.addInterceptor(new RoleInterceptor3()).addPathPatterns("/control/**");
}
所有 preHandle 都返回 true 的执行结果
执行流程及日志输出如下:
preHandle1
preHandle2
preHandle3
......控制器逻辑日志打印.....
postHandle3
postHandle2
postHandle1
...........
afterCompletion3
afterCompletion2
afterCompletion1
规律总结:
- preHandle 方法:按拦截器注册顺序执行(先注册先执行);
- postHandle 方法:按拦截器注册逆序执行(后注册先执行);
- afterCompletion 方法:按拦截器注册逆序执行(后注册先执行)。
某一个 preHandle 返回 false 的执行结果
修改 RoleInterceptor2 的 preHandle 方法,使其返回 false:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle2");
return false; // 返回 false
}
执行结果如下:
preHandle1
preHandle2
afterCompletion1
规律总结:
- 当某个拦截器的 preHandle 返回 false 时,后续的所有拦截器的 preHandle 方法都不会执行;
- 控制器核心逻辑、后续拦截器的 postHandle 方法也不会执行;
- 仅会执行当前拦截器之前已执行过 preHandle 方法的拦截器的 afterCompletion 方法(按注册逆序)。
图解:

拦截器的应用
如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如apache都具有这个功能,但此处我们演示一下使用拦截器怎么实现。
实现分析:
1、在进入处理器之前记录开始时间,即在拦截器的preHandle记录开始时间;
2、在结束请求处理之后记录结束时间,即在拦截器的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。
问题:
我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?
解决方案是使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)。
package com.intercreptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.Nullable;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
public class StopWatchInterceptor implements HandlerInterceptor {
// 线程局部变量,拦截器是单例形式存在,所以每一个请求的开始时间应该绑定在当前线程上
// NameThreadLocal 与 ThreadLocal 的区别:NameThreadLocal 可以为线程绑定变量,且变量名可以自定义,而 ThreadLocal 只能绑定变量
private static NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 处理器执行之前获取系统当前时间(开始时间)
var begin = System.currentTimeMillis();
startTimeThreadLocal.set(begin); //线程绑定变量(该数据只有当前请求的线程可见)
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
// 处理器执行之后获取系统当前时间(结束时间)
var end = System.currentTimeMillis();
var begin = startTimeThreadLocal.get();
log.info("请求耗时:{}ms", end - begin);
startTimeThreadLocal.remove(); // 移除线程变量
}
}
同样需要配置该拦截器,与上面一致。

浙公网安备 33010602011771号