基础
拦截器的生效位置

为什么
preHandle是主流?(核心原因)以后面的案例的「token 校验」为例,就能明白为什么大家优先用
preHandle:1. 「提前拦截」= 减少无效消耗(最核心)
如果 token 无效 / 不存在,
preHandle返回false,请求会直接被拦截,不会进入控制器、不会调用 Service/DAO、不会操作数据库------ 相当于 "把坏人挡在门外",而不是 "让坏人进屋里再赶出去"。
- 比如:用户拿失效的 token 访问
/api/user/info,preHandle直接返回 401,控制器的getUserInfo()方法根本不会执行,节省了服务器资源;- 如果用
postHandle:控制器方法已经执行完(比如查了数据库、查了用户信息),再发现 token 无效,此时只能修改响应,但数据库查询、业务逻辑执行的资源已经消耗了,完全没必要。2.
preHandle是唯一能「阻断请求」的方法
postHandle和afterCompletion都是void返回值,哪怕发现 token 无效,也无法阻止请求流程(因为控制器已经执行了),只能做一些 "补救"(比如修改响应体),但核心业务逻辑已经执行,失去了拦截的意义。而preHandle的boolean返回值是 "生死开关",这是实现「权限控制、token 校验、请求限流」等核心拦截逻辑的关键。3. 符合 "拦截器" 的设计初衷
拦截器(Interceptor)的核心目的是「在请求处理前做校验 / 过滤,避免无效请求消耗资源」,而不是 "请求处理后做善后"------
preHandle完美契合这个初衷,而另外两个方法只是补充。
postHandle和afterCompletion什么时候用?不是说这两个方法没用,而是它们的场景很特定,只适合「后置处理」,比如:
1.
postHandle适用场景
- 统一修改响应数据(比如给所有接口返回值加统一的包装格式:
{code:200, data:..., msg:"success"});- 修改视图渲染参数(比如给所有页面添加全局的用户信息);
- 记录控制器执行耗时(结合
preHandle记录的开始时间,计算耗时)。2.
afterCompletion适用场景
- 资源清理(比如关闭请求中打开的流、释放临时文件);
- 全局异常记录(通过方法参数
Exception ex捕获整个请求流程的异常,记录日志);- 最终的请求日志汇总(比如记录请求的最终状态、响应码)。
1. 拦截器的使用步骤
1.1 创建拦截器
-
第1步:工程目录下创建拦截器
base.interceptors.MyInterCeptor(命名随意)public class MyInterceptor implements HandlerInterceptor {
/**
* 1.执行handler之前调用的拦截方法;
* 2.可以进行编码格式设置、登录保护、权限处理等。
* @param request 请求对象
* @param response 响应对象
* @param handler handler是我们要调用的方法对象
* @return true放行,false拦截
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("1.preHandle执行");
return true;
}/** * 1.当handler方法执行完成后,触发的方法,没有拦截机制了。 * 2.此方法只有 preHandle中 return true时才会触发 * 3.对结果进行处理,比如敏感词检查。 * @param request 请求 * @param response 响应 * @param handler handler方法 * @param modelAndView 返回的视图和模型数据 * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("2.postHandle执行"); } /** * 整体处理完毕,调用此方法 * @param request 请求 * @param response 响应 * @param handler handler方法 * @param ex 异常对象 * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("3.afterCompletion执行"); }}
-
第2步:注册拦截器,工程目录下创建
config.MvcConfig(命名随意)-
第1:实现WebMvcConfigurer,它是SpringMVC的标准化配置类,提供拦截器的注入
-
第2:重写注入方法:addInterceptors
-
第3:注册拦截器:registry.addInterceptor()
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//1.拦截所有请求
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**");
}
}
-
1.2 注册拦截器
在 SpringMVC 的配置类中,通过实现 WebMvcConfigurer 接口 并重写 addInterceptors 方法进行拦截器的注册。
-
第1步:创建配置类,并实现
WebMvcConfigurer -
第2步:注册拦截器,重写
addInterceptors@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//1.拦截所有请求
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/v1/**");
}
}
1.2.1 拦截器配置方案
1.拦截全部请求
registry.addInterceptor(new MyInterceptor()) .addPathPatterns("/**");
2.拦截指定请求
示例: 拦截所有 /v1/user/ 开头的请求,使用 /v1/user/**
registry.addInterceptor(new MyInterceptor()) .addPathPatterns("/v1/user/**");
3.排除指定请求
示例: 拦截所有的 /v1/user/ 开头的请求, 但是不包含登录请求 /v1/user/login
registry
.addInterceptor(new MyInterceptor())
.addPathPatterns("/v1/user/**")
.excludePathPatterns("/v1/user/login");
实战
2. 限定访问时间拦截器
要求: 项目工程中,限定所有请求只能在每天的: 06:00 - 22:59 之间进行访问;
-
第1步:创建拦截器的类并实现拦截器功能;
public class TimeAccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入访问时间限定拦截器");
LocalTime now = LocalTime.now();//JDK中的时间对象
int hour = now.getHour();//获取当前时间对应小时
if (hour<6 || hour>22){
throw new RuntimeException("请在06:00~22:00之间进行访问");
}
return true;
}
} -
第2步:注册拦截器并添加拦截策略;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TimeAccessInterceptor())
.addPathPatterns("/v1/user/**");
}
} -
第3步:重启工程测

3. 权限校验拦截器
由后端进⾏⽤⼾⾝份验证,若不存在token或失效,则失败返回
采⽤拦截器来完成校验token的合法性
第1步:创建拦截器的类并实现拦截器功能;
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//从 header 中获取 token
String jwtToken = request.getHeader("user_token");
log.info("获取路径:{}", request.getRequestURI());
log.info("从header中获取token:{}", jwtToken);
//验证用户 token
Claims claims = JWTUtil.parseJWT(jwtToken);
if (null == claims){
response.setStatus(401);
return false;
}
log.info("令牌验证通过, 放行");
return true;
}
}
第2步:注册拦截器并添加拦截策略;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
private final List<String> excludes = Arrays.asList(
"/**/*.html",
"/css/**",
"/js/**",
"/pic/**",
"/*.jpg",
"/favicon.ico",
"/**/login",
"/register",
"/verification-code/send",
"/winning-records/show"
);
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludes);
}
}
4. 限流功能拦截器[扩展]
因为系统处理能力有限,所以现在要对接口访问进行限流,实现方案是编写一个拦截器,对请求进行拦截和计算,要求: 单个IP限制在每分钟访问网站不能超过3次,超过3次则认为该客户端存在攻击风险,具体代码如下:
-
前提: 获取客户端的IP地址 request.getRemoteAddr();
-
计数的HashMap:
{"192.168.1.11": 2, "192.168.1.12": 1, "192.168.1.13": 3}
-
记录时间的HashMap:
{"192.168.1.11": "2025-08-11 10:00:00", "192.168.1.12": "2025-08-11 11:00:00", "192.168.1.13": "2025-08-11 09:00:00"}
-
情况1: 该IP第一次访问网站;
-
情况2: 该IP之前访问过,但是距离本次访问的时间已经超过了60秒;
-
情况3: 该IP在1分钟之内访问过;
-
获取到客户端的IP地址: request.getRemoteAddr();
-
存储计数的HashMap: {"192.168.1.11": 2, "192.168.1.12": 3, "192.168.1.13": 1}
-
存储最后一次访问时间的HashMap: {"192.168.1.11": "2025-07-11 00:00:00", "192.168.1.12":"2025-07-10:00:00:00", "192.168.1.13": "2020-01-01 00:00:00"}
-
第1步: 编写拦截器
package cn.tedu._03vehicle.base.interceptors; import org.springframework.web.servlet.HandlerInterceptor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.time.LocalDateTime; import java.util.concurrent.ConcurrentHashMap; public class RateLimitInterceptor implements HandlerInterceptor { private static final int MAX_REQUESTS_PER_MINUTE = 3; private static final long WINDOW_SIZE_SECONDS = 60; // {"192.168.1.11": 1, "192.168.1.12": 1, "192.168.1.13": 2} private final ConcurrentHashMap<String, Integer> clientCounts = new ConcurrentHashMap<>(); // {"192.168.1.11": "2024-01-01 14:00:00", "192.168.1.12": "2024-01-01 14:30:30", ...} private final ConcurrentHashMap<String, LocalDateTime> lastResetTimes = new ConcurrentHashMap<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取客户端的IP地址 String clientId = request.getRemoteAddr(); // 尝试获取上次重置时间 LocalDateTime lastResetTime = lastResetTimes.get(clientId); // 如果上次重置时间不存在或已过期,则重置计数器和时间 if (lastResetTime == null || LocalDateTime.now().isAfter(lastResetTime.plusSeconds(WINDOW_SIZE_SECONDS))) { clientCounts.put(clientId, 0); lastResetTimes.put(clientId, LocalDateTime.now()); } // 增加请求次数 Integer count = clientCounts.get(clientId); count = count + 1; clientCounts.put(clientId, count); // 检查是否超过限制 if (count > MAX_REQUESTS_PER_MINUTE) { throw new RuntimeException("访问太频繁了"); } return true; } } -
第2步: 配置拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RateLimitInterceptor())
.addPathPatterns("/v1/user/**");
}
} -
第3步: 重启工程测试