SpringBoot之用拦截器避免重复请求
创始人
2024-02-06 20:41:53

拦截器

什么是拦截器

Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。

如何自定义拦截器

自定义一个拦截器非常简单,只需要实现HandlerInterceptor这个接口即可,这个接口有三个可实现的方法

  1. preHandle()方法:该方法会在控制器方法前执行,其返回值表示是否知道如何写一个接口。中断后续操作。当其返回值为true时,表示继续向下执行;当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。

  2. postHandle()方法:该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改。

  3. afterCompletion()方法:该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作。

如何让拦截器在Spring Boot中生效

想要在Spring Boot生效其实很简单,只需要定义一个配置类,实现WebMvcConfigurer这个接口,并且实现其中的addInterceptors()方法即可,代码如下:

@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate XXX xxx;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 不拦截的urifinal String[] commonExclude = {}};registry.addInterceptor(xxx).excludePathPatterns(commonExclude);}
}

用拦截器规避重复请求

需求

开发中可能会经常遇到短时间内由于用户的重复点击导致几秒之内重复的请求,可能就是在这几秒之内由于各种问题,比如网络事务的隔离性等等问题导致了数据的重复等问题,因此在日常开发中必须规避这类的重复请求操作,今天就用拦截器简单的处理一下这个问题。

思路

在接口执行之前先对指定接口(比如标注某个注解的接口)进行判断,如果在指定的时间内(比如5秒)已经请求过一次了,则返回重复提交的信息给调用者。

根据什么判断这个接口已经请求了?

根据项目的架构可能判断的条件也是不同的,比如IP地址用户唯一标识请求参数请求URI等等其中的某一个或者多个的组合。

这个具体的信息存放在哪?

由于是短时间内甚至是瞬间并且要保证定时失效,肯定不能存在事务性数据库中了,因此常用的几种数据库中只有Redis比较合适了。

实现

Docker启动一个Redis

docker pull redis:7.0.4docker run -itd \--name redis \-p 6379:6379 \redis:7.0.4

创建一个Spring Boot项目

使用idea的Spring Initializr来创建一个Spring Boot项目,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DSz8yzFy-1669041488871)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6c41a65f5d08405e9382150f8e23ffe7~tplv-k3u1fbpfcp-watermark.image?)]

添加依赖

pom.xml文件如下


4.0.0org.springframework.bootspring-boot-starter-parent2.7.5 com.examplespringboot_060.0.1-SNAPSHOTspringboot_06Demo project for Spring Boot1.8org.springframework.bootspring-boot-starter-weborg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-data-redisredis.clientsjedisio.lettucelettuce-coreredis.clientsjedisorg.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-engineorg.springframework.bootspring-boot-maven-plugin

配置Redis

application.properties

spring.redis.host=127.0.0.1
spring.redis.database=1
spring.redis.port=6379

定义一个注解

package com.example.springboot_06.intercept;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {/*** 默认失效时间5秒** @return*/long seconds() default 5;
}

创建一个拦截器

package com.example.springboot_06.intercept;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.concurrent.TimeUnit;/*** 重复请求的拦截器** @Component:该注解将其注入到IOC容器中*/
@Slf4j
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {/*** Redis的API*/@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** preHandler方法,在controller方法之前执行* 

* 判断条件仅仅是用了uri,实际开发中根据实际情况组合一个唯一识别的条件。*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod) {// 只拦截标注了@RepeatSubmit该注解HandlerMethod method = (HandlerMethod) handler;// 标注在方法上的@RepeatSubmitRepeatSubmit repeatSubmitByMethod = AnnotationUtils.findAnnotation(method.getMethod(), RepeatSubmit.class);// 标注在controler类上的@RepeatSubmitRepeatSubmit repeatSubmitByCls = AnnotationUtils.findAnnotation(method.getMethod().getDeclaringClass(), RepeatSubmit.class);// 没有限制重复提交,直接跳过if (Objects.isNull(repeatSubmitByMethod) && Objects.isNull(repeatSubmitByCls)) {log.info("isNull");return true;}// todo: 组合判断条件,这里仅仅是演示,实际项目中根据架构组合条件//请求的URIString uri = request.getRequestURI();//存在即返回false,不存在即返回trueBoolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(uri, "",Objects.nonNull(repeatSubmitByMethod) ? repeatSubmitByMethod.seconds() : repeatSubmitByCls.seconds(), TimeUnit.SECONDS);//如果存在,表示已经请求过了,直接抛出异常,由全局异常进行处理返回指定信息if (ifAbsent != null && !ifAbsent) {String msg = String.format("url:[%s]重复请求", uri);log.warn(msg);// throw new RepeatSubmitException(msg);throw new Exception(msg);}}return true;} }

配置拦截器

package com.example.springboot_06.config;
import com.example.springboot_06.intercept.RepeatSubmitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate RepeatSubmitInterceptor repeatSubmitInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 不拦截的urifinal String[] commonExclude = {"/error", "/files/**"};registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(commonExclude);}
}

写个测试Controller

package com.example.springboot_06.controller;import com.example.springboot_06.intercept.RepeatSubmit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 标注了@RepeatSubmit注解,全部的接口都需要拦截**/
@Slf4j
@RestController
@RequestMapping("/user")
@RepeatSubmit
public class UserController {@RequestMapping("/save")public ResponseEntity save() {log.info("/user/save");return ResponseEntity.ok("save success");}
}

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FzbHqFbA-1669041488871)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/379d50f9afff46ad87516f80a5b6568b~tplv-k3u1fbpfcp-watermark.image?)]

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...