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í)行之前先對指定接口(比如標注某個注解的接口)進行判斷,如果在指定的時間內(nèi)(比如5秒)已經(jīng)請求過一次了,則返回重復(fù)提交的信息給調(diào)用者。
根據(jù)什么判斷這個接口已經(jīng)請求了?
根據(jù)項目的架構(gòu)可能判斷的條件也是不同的,比如IP地址,用戶唯一標識、請求參數(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) {
// 只攔截標注了@RepeatSubmit該注解
HandlerMethod method = (HandlerMethod) handler;
// 標注在方法上的@RepeatSubmit
RepeatSubmit repeatSubmitByMethod = AnnotationUtils.findAnnotation(method.getMethod(), RepeatSubmit.class);
// 標注在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;
/**
* 標注了@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)文章
Java代碼規(guī)范與質(zhì)量檢測插件SonarLint的使用
本文主要介紹了Java代碼規(guī)范與質(zhì)量檢測插件SonarLint的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
淺析java修飾符訪問權(quán)限(動力節(jié)點Java學(xué)院整理)
Java有四種訪問權(quán)限,其中三種有訪問權(quán)限修飾符,分別為private,public和protected,還有一種不帶任何修飾符,下面通過本文給大家簡單介紹下java修飾符訪問權(quán)限相關(guān)知識,感興趣的朋友一起學(xué)習(xí)吧2017-04-04
MybatisPlus實現(xiàn)數(shù)據(jù)權(quán)限隔離的示例詳解
Mybatis Plus對Mybatis做了無侵入的增強,非常的好用,今天就給大家介紹它的其中一個實用功能:數(shù)據(jù)權(quán)限插件,感興趣的可以跟隨小編一起了解下2024-04-04
舉例講解Java的Spring框架中AOP程序設(shè)計方式的使用
這篇文章主要介紹了Java的Spring框架中AOP程序設(shè)計方式的使用講解,文中舉的AOP下拋出異常的例子非常實用,需要的朋友可以參考下2016-04-04
Kotlin 基礎(chǔ)教程之?dāng)?shù)組容器
這篇文章主要介紹了Kotlin 基礎(chǔ)教程之?dāng)?shù)組容器的相關(guān)資料,需要的朋友可以參考下2017-06-06

