SpringBoot利用攔截器實(shí)現(xiàn)避免重復(fù)請求
攔截器
什么是攔截器
Spring MVC中的攔截器(Interceptor)類似于Servlet中的過濾器(Filter),它主要用于攔截用戶請求并作相應(yīng)的處理。例如通過攔截器可以進(jìn)行權(quán)限驗(yàn)證、記錄請求信息的日志、判斷用戶是否登錄等。
如何自定義攔截器
自定義一個(gè)攔截器非常簡單,只需要實(shí)現(xiàn)HandlerInterceptor這個(gè)接口即可,這個(gè)接口有三個(gè)可實(shí)現(xiàn)的方法
preHandle()方法:該方法會(huì)在控制器方法前執(zhí)行,其返回值表示是否知道如何寫一個(gè)接口。中斷后續(xù)操作。當(dāng)其返回值為true時(shí),表示繼續(xù)向下執(zhí)行;當(dāng)其返回值為false時(shí),會(huì)中斷后續(xù)的所有操作(包括調(diào)用下一個(gè)攔截器和控制器類中的方法執(zhí)行等)。postHandle()方法:該方法會(huì)在控制器方法調(diào)用之后,且解析視圖之前執(zhí)行。可以通過此方法對(duì)請求域中的模型和視圖做出進(jìn)一步的修改。afterCompletion()方法:該方法會(huì)在整個(gè)請求完成,即視圖渲染結(jié)束之后執(zhí)行??梢酝ㄟ^此方法實(shí)現(xiàn)一些資源清理、記錄日志信息等工作。
如何讓攔截器在Spring Boot中生效
想要在Spring Boot生效其實(shí)很簡單,只需要定義一個(gè)配置類,實(shí)現(xiàn)WebMvcConfigurer這個(gè)接口,并且實(shí)現(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ā)中可能會(huì)經(jīng)常遇到短時(shí)間內(nèi)由于用戶的重復(fù)點(diǎn)擊導(dǎo)致幾秒之內(nèi)重復(fù)的請求,可能就是在這幾秒之內(nèi)由于各種問題,比如網(wǎng)絡(luò),事務(wù)的隔離性等等問題導(dǎo)致了數(shù)據(jù)的重復(fù)等問題,因此在日常開發(fā)中必須規(guī)避這類的重復(fù)請求操作,今天就用攔截器簡單的處理一下這個(gè)問題。
思路
在接口執(zhí)行之前先對(duì)指定接口(比如標(biāo)注某個(gè)注解的接口)進(jìn)行判斷,如果在指定的時(shí)間內(nèi)(比如5秒)已經(jīng)請求過一次了,則返回重復(fù)提交的信息給調(diào)用者。
根據(jù)什么判斷這個(gè)接口已經(jīng)請求了?
根據(jù)項(xiàng)目的架構(gòu)可能判斷的條件也是不同的,比如IP地址,用戶唯一標(biāo)識(shí)、請求參數(shù)、請求URI等等其中的某一個(gè)或者多個(gè)的組合。
這個(gè)具體的信息存放在哪?
由于是短時(shí)間內(nèi)甚至是瞬間并且要保證定時(shí)失效,肯定不能存在事務(wù)性數(shù)據(jù)庫中了,因此常用的幾種數(shù)據(jù)庫中只有Redis比較合適了。
實(shí)現(xiàn)
Docker啟動(dòng)一個(gè)Redis
docker pull redis:7.0.4
docker run -itd \
--name redis \
-p 6379:6379 \
redis:7.0.4創(chuàng)建一個(gè)Spring Boot項(xiàng)目
使用idea的Spring Initializr來創(chuàng)建一個(gè)Spring Boot項(xiàng)目,如下圖:

添加依賴
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的版本默認(rèn)采用的連接池技術(shù)是jedis 2.0以上版本默認(rèn)連接池是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
定義一個(gè)注解
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 {
/**
* 默認(rèn)失效時(shí)間5秒
*
* @return
*/
long seconds() default 5;
}創(chuàng)建一個(gè)攔截器
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,實(shí)際開發(fā)中根據(jù)實(shí)際情況組合一個(gè)唯一識(shí)別的條件。
*/
@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: 組合判斷條件,這里僅僅是演示,實(shí)際項(xiàng)目中根據(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)請求過了,直接拋出異常,由全局異常進(jìn)行處理返回指定信息
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);
}
}寫個(gè)測試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利用攔截器實(shí)現(xiàn)避免重復(fù)請求的文章就介紹到這了,更多相關(guān)SpringBoot攔截器防重復(fù)請求內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis-Plus中update()和updateById()將字段更新為null
本文主要介紹了Mybatis-Plus中update()和updateById()將字段更新為null,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Mybatis查詢返回Map<String,Object>類型的實(shí)現(xiàn)
本文主要介紹了Mybatis查詢返回Map<String,Object>類型的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
springboot結(jié)合mysql主從來實(shí)現(xiàn)讀寫分離的方法示例
這篇文章主要介紹了springboot結(jié)合mysql主從來實(shí)現(xiàn)讀寫分離的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Mybatis-plus多租戶項(xiàng)目實(shí)戰(zhàn)進(jìn)階指南
多租戶是一種軟件架構(gòu)技術(shù),在多用戶的環(huán)境下共有同一套系統(tǒng),并且要注意數(shù)據(jù)之間的隔離性,下面這篇文章主要給大家介紹了關(guān)于Mybatis-plus多租戶項(xiàng)目實(shí)戰(zhàn)進(jìn)階的相關(guān)資料,需要的朋友可以參考下2022-02-02
Java Quartz觸發(fā)器CronTriggerBean配置用法詳解
這篇文章主要介紹了Java Quartz觸發(fā)器CronTriggerBean配置用法詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
Spring Boot通過Junit實(shí)現(xiàn)單元測試過程解析
這篇文章主要介紹了Spring Boot通過Junit實(shí)現(xiàn)單元測試過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
解析MapStruct轉(zhuǎn)換javaBean時(shí)出現(xiàn)的詭異事件
在項(xiàng)目中用到了MapStruct,對(duì)其可以轉(zhuǎn)換JavaBean特別好奇,今天小編給大家分享一個(gè)demo給大家講解MapStruct轉(zhuǎn)換javaBean時(shí)出現(xiàn)的詭異事件,感興趣的朋友一起看看吧2021-09-09
Spring三級(jí)緩存思想解決循環(huán)依賴總結(jié)分析
這篇文章主要為大家介紹了Spring三級(jí)緩存思想解決循環(huán)依賴總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08

