SpringBoot利用攔截器實現(xiàn)避免重復(fù)請求
攔截器
什么是攔截器
Spring MVC中的攔截器(Interceptor)類似于Servlet中的過濾器(Filter),它主要用于攔截用戶請求并作相應(yīng)的處理。例如通過攔截器可以進行權(quán)限驗證、記錄請求信息的日志、判斷用戶是否登錄等。
如何自定義攔截器
自定義一個攔截器非常簡單,只需要實現(xiàn)HandlerInterceptor這個接口即可,這個接口有三個可實現(xiàn)的方法
preHandle()
方法:該方法會在控制器方法前執(zhí)行,其返回值表示是否知道如何寫一個接口。中斷后續(xù)操作。當(dāng)其返回值為true
時,表示繼續(xù)向下執(zhí)行;當(dāng)其返回值為false
時,會中斷后續(xù)的所有操作(包括調(diào)用下一個攔截器和控制器類中的方法執(zhí)行等)。postHandle()
方法:該方法會在控制器方法調(diào)用之后,且解析視圖之前執(zhí)行??梢酝ㄟ^此方法對請求域中的模型和視圖做出進一步的修改。afterCompletion()
方法:該方法會在整個請求完成,即視圖渲染結(jié)束之后執(zhí)行??梢酝ㄟ^此方法實現(xiàn)一些資源清理、記錄日志信息等工作。
如何讓攔截器在Spring Boot中生效
想要在Spring Boot生效其實很簡單,只需要定義一個配置類,實現(xiàn)WebMvcConfigurer
這個接口,并且實現(xiàn)其中的addInterceptors()
方法即可,代碼如下:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private XXX xxx; @Override public void addInterceptors(InterceptorRegistry registry) { // 不攔截的uri final String[] commonExclude = {}}; registry.addInterceptor(xxx).excludePathPatterns(commonExclude); } }
用攔截器規(guī)避重復(fù)請求
需求
開發(fā)中可能會經(jīng)常遇到短時間內(nèi)由于用戶的重復(fù)點擊導(dǎo)致幾秒之內(nèi)重復(fù)的請求,可能就是在這幾秒之內(nèi)由于各種問題,比如網(wǎng)絡(luò),事務(wù)的隔離性等等問題導(dǎo)致了數(shù)據(jù)的重復(fù)等問題,因此在日常開發(fā)中必須規(guī)避這類的重復(fù)請求操作,今天就用攔截器簡單的處理一下這個問題。
思路
在接口執(zhí)行之前先對指定接口(比如標(biāo)注某個注解的接口)進行判斷,如果在指定的時間內(nèi)(比如5秒)已經(jīng)請求過一次了,則返回重復(fù)提交的信息給調(diào)用者。
根據(jù)什么判斷這個接口已經(jīng)請求了?
根據(jù)項目的架構(gòu)可能判斷的條件也是不同的,比如IP地址,用戶唯一標(biāo)識、請求參數(shù)、請求URI等等其中的某一個或者多個的組合。
這個具體的信息存放在哪?
由于是短時間內(nèi)甚至是瞬間并且要保證定時失效,肯定不能存在事務(wù)性數(shù)據(jù)庫中了,因此常用的幾種數(shù)據(jù)庫中只有Redis比較合適了。
實現(xiàn)
Docker啟動一個Redis
docker pull redis:7.0.4 docker run -itd \ --name redis \ -p 6379:6379 \ redis:7.0.4
創(chuàng)建一個Spring Boot項目
使用idea的Spring Initializr來創(chuàng)建一個Spring Boot項目,如下圖:
添加依賴
pom.xml文件如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springboot_06</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot_06</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--spring redis配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!-- 1.5的版本默認采用的連接池技術(shù)是jedis 2.0以上版本默認連接池是lettuce, 在這里采用jedis,所以需要排除lettuce的jar --> <exclusions> <exclusion> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </exclusion> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置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; }
創(chuàng)建一個攔截器
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; /** * 重復(fù)請求的攔截器 * * @Component:該注解將其注入到IOC容器中 */ @Slf4j @Component public class RepeatSubmitInterceptor implements HandlerInterceptor { /** * Redis的API */ @Autowired private StringRedisTemplate stringRedisTemplate; /** * preHandler方法,在controller方法之前執(zhí)行 * <p> * 判斷條件僅僅是用了uri,實際開發(fā)中根據(jù)實際情況組合一個唯一識別的條件。 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { // 只攔截標(biāo)注了@RepeatSubmit該注解 HandlerMethod method = (HandlerMethod) handler; // 標(biāo)注在方法上的@RepeatSubmit RepeatSubmit repeatSubmitByMethod = AnnotationUtils.findAnnotation(method.getMethod(), RepeatSubmit.class); // 標(biāo)注在controler類上的@RepeatSubmit RepeatSubmit repeatSubmitByCls = AnnotationUtils.findAnnotation(method.getMethod().getDeclaringClass(), RepeatSubmit.class); // 沒有限制重復(fù)提交,直接跳過 if (Objects.isNull(repeatSubmitByMethod) && Objects.isNull(repeatSubmitByCls)) { log.info("isNull"); return true; } // todo: 組合判斷條件,這里僅僅是演示,實際項目中根據(jù)架構(gòu)組合條件 //請求的URI String uri = request.getRequestURI(); //存在即返回false,不存在即返回true Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(uri, "", Objects.nonNull(repeatSubmitByMethod) ? repeatSubmitByMethod.seconds() : repeatSubmitByCls.seconds(), TimeUnit.SECONDS); //如果存在,表示已經(jīng)請求過了,直接拋出異常,由全局異常進行處理返回指定信息 if (ifAbsent != null && !ifAbsent) { String msg = String.format("url:[%s]重復(fù)請求", 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 { @Autowired private RepeatSubmitInterceptor repeatSubmitInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { // 不攔截的uri final 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; /** * 標(biāo)注了@RepeatSubmit注解,全部的接口都需要攔截 * */ @Slf4j @RestController @RequestMapping("/user") @RepeatSubmit public class UserController { @RequestMapping("/save") public ResponseEntity save() { log.info("/user/save"); return ResponseEntity.ok("save success"); } }
測試
到此這篇關(guān)于SpringBoot利用攔截器實現(xiàn)避免重復(fù)請求的文章就介紹到這了,更多相關(guān)SpringBoot攔截器防重復(fù)請求內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis-Plus中update()和updateById()將字段更新為null
本文主要介紹了Mybatis-Plus中update()和updateById()將字段更新為null,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Mybatis查詢返回Map<String,Object>類型的實現(xiàn)
本文主要介紹了Mybatis查詢返回Map<String,Object>類型的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07springboot結(jié)合mysql主從來實現(xiàn)讀寫分離的方法示例
這篇文章主要介紹了springboot結(jié)合mysql主從來實現(xiàn)讀寫分離的方法示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Java Quartz觸發(fā)器CronTriggerBean配置用法詳解
這篇文章主要介紹了Java Quartz觸發(fā)器CronTriggerBean配置用法詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08Spring Boot通過Junit實現(xiàn)單元測試過程解析
這篇文章主要介紹了Spring Boot通過Junit實現(xiàn)單元測試過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01解析MapStruct轉(zhuǎn)換javaBean時出現(xiàn)的詭異事件
在項目中用到了MapStruct,對其可以轉(zhuǎn)換JavaBean特別好奇,今天小編給大家分享一個demo給大家講解MapStruct轉(zhuǎn)換javaBean時出現(xiàn)的詭異事件,感興趣的朋友一起看看吧2021-09-09Spring三級緩存思想解決循環(huán)依賴總結(jié)分析
這篇文章主要為大家介紹了Spring三級緩存思想解決循環(huán)依賴總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08