interceptor
What is an interceptor
The Interceptor in Spring MVC is similar to the Filter in Servlet. It is mainly used to intercept user requests and process them accordingly. For example, the interceptor can perform authority verification, record the log of request information, and judge whether the user is logged in or not.
How to customize the interceptor
It is very simple to customize an interceptor. You only need to implement the HandlerInterceptor interface. This interface has three implementable methods
-
preHandle() method: This method will be executed before the controller method, and its return value indicates whether it knows how to write an interface. Interrupt subsequent operations. When its return value is true, it means continue to execute downward; when its return value is false, it will interrupt all subsequent operations (including calling the next interceptor and method execution in the controller class, etc.).
-
postHandle() method: This method will be executed after the controller method is called and before the view is parsed. Further modifications to models and views in the request domain can be made through this method.
-
afterCompletion() method: This method will be executed after the entire request is completed, that is, after the view rendering ends. You can use this method to achieve some resource cleaning, log information and other tasks.
How to make the interceptor take effect in Spring Boot
It is actually very simple to take effect in Spring Boot. You only need to define a configuration class, implement the WebMvcConfigurer interface, and implement the addInterceptors() method. The code is as follows:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private XXX xxx; @Override public void addInterceptors(InterceptorRegistry registry) { // uri not intercepted final String[] commonExclude = {}}; registry.addInterceptor(xxx).excludePathPatterns(commonExclude); } }
Avoid duplicate requests with interceptors
need
In development, you may often encounter repeated requests within a few seconds due to repeated clicks by users in a short period of time. It may be that within a few seconds, due to various problems, such as network, transaction isolation, etc., the data Therefore, in daily development, such repeated request operations must be avoided. Today, we will simply use interceptors to deal with this problem.
train of thought
Before the interface is executed, the specified interface (such as the interface marked with a certain annotation) is judged. If it has been requested once within the specified time (such as 5 seconds), the repeated submission information will be returned to the caller.
What is the basis for judging that this interface has been requested?
The conditions that may be judged according to the structure of the project are also different, such as IP address, unique user ID, request parameters, request URI, etc., one or more of them.
Where is this specific information stored?
Since it is short-term or even instantaneous and must be guaranteed to fail at regular intervals, it must not exist in a transactional database. Therefore, among the commonly used databases, only Redis is more suitable.
accomplish
Docker starts a Redis
docker pull redis:7.0.4 docker run -itd \ --name redis \ -p 6379:6379 \ redis:7.0.4
Create a Spring Boot project
Use idea's Spring Initializr to create a Spring Boot project, as shown below:
add dependencies
The pom.xml file is as follows
<?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 configuration--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!-- 1.5 The connection pool technology used by default in the version is jedis 2.0 The default connection pool of the above version is lettuce, adopt here jedis,So need to exclude lettuce of 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>
Configure Redis
application.properties
spring.redis.host=127.0.0.1 spring.redis.database=1 spring.redis.port=6379
define an annotation
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 { /** * The default expiration time is 5 seconds * * @return */ long seconds() default 5; }
Create an interceptor
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; /** * Interceptor for repeated requests * * @Component: This annotation injects it into the IOC container */ @Slf4j @Component public class RepeatSubmitInterceptor implements HandlerInterceptor { /** * Redis API */ @Autowired private StringRedisTemplate stringRedisTemplate; /** * preHandler method, executed before the controller method * <p> * The judgment condition only uses uri, and a unique identification condition is combined according to the actual situation in actual development. */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { // Only intercept the annotation marked @RepeatSubmit HandlerMethod method = (HandlerMethod) handler; // @RepeatSubmit marked on the method RepeatSubmit repeatSubmitByMethod = AnnotationUtils.findAnnotation(method.getMethod(), RepeatSubmit.class); // @RepeatSubmit marked on the controller class RepeatSubmit repeatSubmitByCls = AnnotationUtils.findAnnotation(method.getMethod().getDeclaringClass(), RepeatSubmit.class); // There is no limit to repeated submissions, skip directly if (Objects.isNull(repeatSubmitByMethod) && Objects.isNull(repeatSubmitByCls)) { log.info("isNull"); return true; } // todo: Combination judgment conditions, here is just a demonstration, in actual projects, the conditions are combined according to the architecture //Request URI String uri = request.getRequestURI(); //Returns false if it exists, returns true if it does not exist Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(uri, "", Objects.nonNull(repeatSubmitByMethod) ? repeatSubmitByMethod.seconds() : repeatSubmitByCls.seconds(), TimeUnit.SECONDS); //If it exists, it means that the request has been made, an exception is thrown directly, and the specified information is returned for processing by the global exception if (ifAbsent != null && !ifAbsent) { String msg = String.format("url:[%s]Repeat request", uri); log.warn(msg); // throw new RepeatSubmitException(msg); throw new Exception(msg); } } return true; } }
Configure interceptors
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 not intercepted final String[] commonExclude = {"/error", "/files/**"}; registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(commonExclude); } }
Write a test 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; /** * Annotated with @RepeatSubmit, all interfaces need to be intercepted * */ @Slf4j @RestController @RequestMapping("/user") @RepeatSubmit public class UserController { @RequestMapping("/save") public ResponseEntity save() { log.info("/user/save"); return ResponseEntity.ok("save success"); } }