catalogue
-
preface
-
Spring Boot version
-
What is an interceptor?
-
How to customize an interceptor?
-
How to make it work in Spring Boot?
-
Take a chestnut
-
thinking
-
According to what judgment, this interface has been requested?
-
Where is this specific information stored?
-
How?
-
-
summary
preface
The last article talked about the basic content of WEB development of Spring Boot. I believe readers have a preliminary understanding and know how to write an interface.
Today's article introduces how to customize and configure interceptors in Spring Boot.
Spring Boot version
The version of Spring Boot based on this article is 2.3.4.RELEASE.
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, through the Interceptor, you can verify permissions, log request information, judge whether users log in, etc.
How to customize an interceptor?
Customizing an interceptor is very simple. You only need to implement the HandlerInterceptor interface. The interface has three methods that can be implemented, as follows:
-
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 the return value is true, it means to continue downward execution; When the return value is false, all subsequent operations will be interrupted (including calling the next interceptor and method execution in the controller class).
-
postHandle() method: this method will be executed after the controller method is called and before the view is parsed. You can use this method to make further modifications to the model and view in the request domain.
-
afterCompletion() method: this method will be executed after the entire request is completed, that is, after the view rendering is completed. This method can be used to clean up resources and record log information.
How to make it work in Spring Boot?
In fact, it is 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 shown as follows:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private XXX xxx; @Override public void addInterceptors(InterceptorRegistry registry) { //Non intercepted uri final String[] commonExclude = {}}; registry.addInterceptor(xxx).excludePathPatterns(commonExclude); } }
Take a chestnut
In the development, we may often encounter repeated requests within a few seconds due to repeated clicks of users in a short time. It may be that various problems, such as network and transaction isolation, lead to data duplication and other problems within these seconds. Therefore, we must avoid such repeated request operations in daily development, Let's simply deal with this problem with interceptors today.
thinking
Judge the specified interface (such as the interface marked with an annotation) before interface execution. If it has been requested once within the specified time (such as 5 seconds), the repeatedly submitted information will be returned to the caller.
According to what judgment, this interface has been requested?
The conditions may be different according to the project architecture, such as IP address, user unique ID, request parameter, request URI, etc.
Where is this specific information stored?
Because it is in a short time or even in an instant, and the timing failure must be guaranteed, it must not exist in the transactional database. Therefore, among the commonly used databases, only Redis is more appropriate.
How?
The first step is to customize an annotation, which can be marked on a class or method, as follows:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RepeatSubmit { /** * The default expiration time is 5 seconds */ long seconds() default 5; }
The second step is to create an interceptor and inject it into the IOC container. The implementation idea is very simple. Judge whether the @ RepeatSubmit annotation is marked on the controller class or method. If so, intercept the judgment. Otherwise, skip. The code is as follows:
/** * Interceptor for repeated requests * @Component: This annotation injects it into the IOC container */ @Component public class RepeatSubmitInterceptor implements HandlerInterceptor { /** * Redis API for */ @Autowired private StringRedisTemplate stringRedisTemplate; /** * preHandler Method, executed before the controller method * * The judgment condition only uses uri. In actual development, a unique identification condition is combined according to the actual situation. */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod){ //Only the annotation @ RepeatSubmit is intercepted HandlerMethod method=(HandlerMethod)handler; //@ RepeatSubmit marked on 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 restriction on repeated submission. Skip directly if (Objects.isNull(repeatSubmitByMethod)&&Objects.isNull(repeatSubmitByCls)) return true; // todo: The combination judgment condition is just a demonstration. In the actual project, the conditions are combined according to the architecture //Requested URI String uri = request.getRequestURI(); //Returns false if it exists and 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 indicates that the request has been made. An exception is thrown directly, and the global exception is processed to return the specified information if (ifAbsent!=null&&!ifAbsent) throw new RepeatSubmitException(); } return true; } }
Step 3: configure the interceptor in Spring Boot. The code is as follows:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private RepeatSubmitInterceptor repeatSubmitInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { //Non intercepted uri final String[] commonExclude = {"/error", "/files/**"}; registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(commonExclude); } }
OK, the interceptor has been configured. You only need to mark @ RepeatSubmit on the interface to be intercepted, as follows:
@RestController @RequestMapping("/user") //Marked with @ RepeatSubmit annotation, all interfaces need to be intercepted @RepeatSubmit public class LoginController { @RequestMapping("/login") public String login(){ return "login success"; } }
At this point, request this URI:http://localhost:8080/springboot-demo/user/login can only be requested once in 5 seconds.
Note: the timeout time marked on the method will overwrite the time on the class, because the following code:
Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(uri, "", Objects.nonNull(repeatSubmitByMethod)?repeatSubmitByMethod.seconds():repeatSubmitByCls.seconds(), TimeUnit.SECONDS);
The expiration time of this code first takes the value configured in repeatSubmitByMethod. If it is null, it takes the value configured in repeatSubmitByCls.
summary
So far, the content of the interceptor has been introduced. In fact, the configuration is very simple and there is no important content.