1. 概述
Spring MVC
中的拦截器(Interceptor
)基于回调机制,用于在控制器处理请求之前或之后执行某些操作
应用场景
- 权限验证
如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面。 - 日志记录
记录请求信息的日志,以便进行信息监控、信息统计等。 - 性能监控
有时系统在某段时间莫名其妙负载很高,可通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。
2. 拦截器定义
在Spring MVC
中,拦截器定义需要实现 HandlerInterceptor
接口。该接口提供了三个方法:
其中:
-
preHandle()
方法:- 作用:在控制器方法执行前被调用。如果返回
true
,则控制器方法将继续执行;如果返回false
,则会中断后续处理流程,不会调用控制器方法,并且不再执行其他拦截器的postHandle()
和afterCompletion()
方法。 - 方法签名
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{}
- 作用:在控制器方法执行前被调用。如果返回
-
postHandle()
方法:- 作用:在控制器方法执行后,但在视图渲染之前调用。可以用来修改模型数据或视图名称。
- 方法签名
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{}
-
afterCompletion()
方法:- 作用:在整个请求完成后调用,即在视图渲染结束后。这可用于资源清理或记录日志等操作。
- 方法签名
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception{}
3. 注册拦截器配置方案
3.1. 注册拦截器
在 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("/**");
}
}
3.2.配置方案
- 拦截全部请求
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**");
- 拦截指定请求
示例: 拦截所有 /v1/user/
开头的请求,使用 /v1/user/**
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/v1/user/**");
- 排除指定请求
示例: 拦截所有的 /v1/user/
开头的请求, 但是不包含登录请求 /v1/user/login
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/v1/user/**").excludePathPatterns("/v1/user/login");
4. 示例
限流功能拦截器[扩展]
因为系统处理能力有限,所以现在要对接口访问进行限流,实现方案是编写一个拦截器,对请求进行拦截和计算,要求: 单个IP限制在每分钟访问网站不能超过3次,超过3次则认为该客户端存在攻击风险,具体代码如下:
需要记录的信息:
- String --- IP: request.getRemoteAddr(); 192.168.1.11
- 计数: clientCounts {"192.168.1.11": 2}
- 最后一次访问的时间: 2025-02-06 14:00:00 {"192.168.1.11": 2025-02-06 14:00:00}
-
第1步: 编写拦截器
package xxx; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.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()); } // 增加请求次数 int count = clientCounts.merge(clientId, 1, Integer::sum); // 检查是否超过限制 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/**"); } }