攔截器
什么是攔截器
Spring MVC中的攔截器(Interceptor)類似于Servlet中的過濾器(Filter),它主要用于攔截用戶請求并作相應的處理,例如通過攔截器可以進行權限驗證、記錄請求資訊的日志、判斷用戶是否登錄等,
如何自定義攔截器
自定義一個攔截器非常簡單,只需要實作HandlerInterceptor這個介面即可,這個介面有三個可實作的方法
-
preHandle()方法:該方法會在控制器方法前執行,其回傳值表示是否知道如何寫一個介面,中斷后續操作,當其回傳值為true時,表示繼續向下執行;當其回傳值為false時,會中斷后續的所有操作(包括呼叫下一個攔截器和控制器類中的方法執行等), -
postHandle()方法:該方法會在控制器方法呼叫之后,且決議視圖之前執行,可以通過此方法對請求域中的模型和視圖做出進一步的修改, -
afterCompletion()方法:該方法會在整個請求完成,即視圖渲染結束之后執行,可以通過此方法實作一些資源清理、記錄日志資訊等作業,
如何讓攔截器在Spring Boot中生效
想要在Spring Boot生效其實很簡單,只需要定義一個配置類,實作WebMvcConfigurer這個介面,并且實作其中的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);
}
}
用攔截器規避重復請求
需求
開發中可能會經常遇到短時間內由于用戶的重復點擊導致幾秒之內重復的請求,可能就是在這幾秒之內由于各種問題,比如網路,事務的隔離性等等問題導致了資料的重復等問題,因此在日常開發中必須規避這類的重復請求操作,今天就用攔截器簡單的處理一下這個問題,
思路
在介面執行之前先對指定介面(比如標注某個注解的介面)進行判斷,如果在指定的時間內(比如5秒)已經請求過一次了,則回傳重復提交的資訊給呼叫者,
根據什么判斷這個介面已經請求了?
根據專案的架構可能判斷的條件也是不同的,比如IP地址,用戶唯一標識、請求引數、請求URI等等其中的某一個或者多個的組合,
這個具體的資訊存放在哪?
由于是短時間內甚至是瞬間并且要保證定時失效,肯定不能存在事務性資料庫中了,因此常用的幾種資料庫中只有Redis比較合適了,
實作
Docker啟動一個Redis
docker pull redis:7.0.4
docker run -itd \
--name redis \
-p 6379:6379 \
redis:7.0.4
創建一個Spring Boot專案
使用idea的Spring Initializr來創建一個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的版本默認采用的連接池技術是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;
}
創建一個攔截器
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
*/
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* preHandler方法,在controller方法之前執行
* <p>
* 判斷條件僅僅是用了uri,實際開發中根據實際情況組合一個唯一識別的條件,
*/
@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);
// 沒有限制重復提交,直接跳過
if (Objects.isNull(repeatSubmitByMethod) && Objects.isNull(repeatSubmitByCls)) {
log.info("isNull");
return true;
}
// todo: 組合判斷條件,這里僅僅是演示,實際專案中根據架構組合條件
//請求的URI
String uri = request.getRequestURI();
//存在即回傳false,不存在即回傳true
Boolean 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 {
@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");
}
}
測驗
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/537691.html
標籤:其他
上一篇:<二>自己實作簡單的string
下一篇:python中的字串操作
