Spring MVC中的拦截器( Interceptor )类似于Servlet中的过滤器( Filter ),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
自定义一个拦截器
自定义一个拦截器非常简单,只需要实现 HandlerInterceptor 这个接口即可,该接口有三个可以实现的 方法,如下:
preHandle()
方法:该方法会在控制器方法前执行,其返回值表示是否知道如何写一个接口。中断后续操作。当其返回值为 true 时,表示继续向下执行;当其返回值为 false 时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。
postHandle()
方法:该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改。
afterCompletion()
方法:该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作。
在Spring Boot中生效
定义一个配置类,实现 WebMvcConfigurer
这个接口, 并且实现其中的 addInterceptors()
方法即可,代码演示如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class WebConfig implements WebMvcConfigurer {
@Autowired MyInterceptor myInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { final String[] commonExclude = {}; registry.addInterceptor(myInterceptor).excludePathPatterns(commonExclude); } }
|
举个栗子
开发中可能会经常遇到短时间内由于用户的重复点击导致几秒之内重复的请求,可能就是在这几秒之内由于各种问题,比如网络,事务的隔离性等等问题导致了数据的重复等问题,因此在日常开发中必须规避这类的重复请求操作,今天就用拦截器简单的处理一下这个问题。
思路
在接口执行之前先对指定接口(比如标注某个注解的接口)进行判断,如果在指定的时间内(比如5 秒 )已经请求过一次了,则返回重复提交的信息给调用者。
根据什么判断这个接口已经请求了?
根据项目的架构可能判断的条件也是不同的,比如 IP地址 、 用户唯一标识 、 请求参数 、 请求URI 等等其中的某一个或者多个的组合。
这个具体的信息存放在哪里?
由于是 短时间 内甚至是瞬间并且要保证 定时失效 ,肯定不能存在事务性数据库中了,因此常用的几种数据库中只有 Redis 比较合适了。
实现
自定义一个注解
第一步,先自定义一个注解,可以标注在类或者方法上,如下:
1 2 3 4 5 6
| @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RepeatSubmit { long seconds() default 5; }
|
配置Redis
第二步,配置Redis
添加依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
|
配置application.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| spring: redis: host: localhost port: 6379 database: 0 password: timeout: 10s lettuce: pool: min-idle: 0 max-idle: 8 max-active: 8 max-wait: -1ms
|
创建拦截器并注入到IOC容器
第三步,创建一个拦截器,注入到IOC容器中 ,实现的思路很简单,判断controller的类或者方法上是否标注了 @RepeatSubmit 这个注解,如果标注了,则拦截判断,否则跳过,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @Component public class RepeatSubmitInterceptor implements HandlerInterceptor {
@Autowired StringRedisTemplate stringRedisTemplate;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; RepeatSubmit repeatSubmitByMethod = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), RepeatSubmit.class); RepeatSubmit repeatSubmitByClass = AnnotationUtils.findAnnotation(handlerMethod.getMethod().getDeclaringClass(), RepeatSubmit.class); if (Objects.isNull(repeatSubmitByMethod) && Objects.isNull(repeatSubmitByClass)) { return true; } String uri = request.getRequestURI(); Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(uri, "", Objects.nonNull(repeatSubmitByMethod) ? repeatSubmitByMethod.seconds() : repeatSubmitByClass.seconds(), TimeUnit.SECONDS); if (ifAbsent != null && !ifAbsent) { throw new RepeatSubmitException(); } return true; } return true; }
}
|
在WebConfig中配置拦截器
第四步,在WebConfig中配置这个拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class WebConfig implements WebMvcConfigurer {
@Autowired RepeatSubmitInterceptor repeatSubmitInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { final String[] commonExclude = {"/error", "/files/**"}; registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(commonExclude); } }
|
创建Controller
OK,拦截器已经配置完成,只需要在需要拦截的接口上标注 @RepeatSubmit 这个注解即可
1 2 3 4 5 6 7 8 9 10 11
| @RestController @RequestMapping("interceptor") public class InterceptorController {
@GetMapping("hello") @RepeatSubmit public String hello() { return "Hello"; }
}
|
1
| GET http://localhost:8080/interceptor/hello
|
源码
https://github.com/zhaoyangmushiyi/spring-boot-advance/tree/master/sprint-boot-interceptor