Spring MVC 拦截器应用

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/**");
        }
    }
    
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容