Spring Boot version
The version of Spring Boot on which this article is based is 2.3.4.RELEASE.
What is an interceptor?
Interceptor in Spring MVC is similar to Filter in Servlet. It is mainly used to intercept user requests and handle them accordingly. For example, the interceptor can perform permission verification, log the request information, and determine whether the user logs in.
How to customize an interceptor?
Customizing an interceptor is very simple. You only need to implement the HandlerInterceptor interface, which 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 to execute downward; When the return value is false, all subsequent operations (including calling the next interceptor and method execution in the controller class) will be interrupted.
-
postHandle() method: this method will be executed after the controller method is called and before the view is parsed. You can further modify the models and views in the request domain through this method.
-
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 make Spring Boot effective. 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) { //uri not intercepted final String[] commonExclude = {}}; registry.addInterceptor(xxx).excludePathPatterns(commonExclude); } }
Take a chestnut
During development, we may often encounter repeated requests within a few seconds due to repeated clicks of users. It may be that various problems, such as network and transaction isolation, lead to data duplication within a few seconds. Therefore, we must avoid such repeated requests in daily development. Today, we will simply deal with this problem with interceptors.
thinking
Before the interface is executed, judge the specified interface (such as the interface marked with an annotation). If the request has been made once within the specified time (such as 5 seconds), the repeatedly submitted information will be returned to the caller.
What is the basis for judging that this interface has been requested?
Different conditions may be judged according to the project architecture, such as IP address, user unique identifier, request parameter, request URI, etc.
Where is this specific information stored?
Because it is a short time or even an instant, and it is necessary to ensure the timing of failure, it must not exist in the transactional database. Therefore, among the commonly used databases, only Redis is suitable.
How to achieve it?
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 { /** * Default expiration time: 5 seconds */ long seconds() default 5; }
Step 2: 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 and judge. 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 is only 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 @ RepeatSubmit annotation is intercepted HandlerMethod method=(HandlerMethod)handler; //@ RepeatSubmit annotated 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 on repeated submission, skip directly if (Objects.isNull(repeatSubmitByMethod)&&Objects.isNull(repeatSubmitByCls)) return true; //todo: combination judgment conditions. This is just a demonstration. In actual projects, conditions are combined according to the architecture //Requested URI String uri = request.getRequestURI(); //Returns false if it exists, and 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 indicates that the request has been made, and an exception is thrown directly. 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) { //uri not intercepted final String[] commonExclude = {"/error", "/files/**"}; registry.addInterceptor(repeatSubmitInterceptor).excludePathPatterns(commonExclude); } }
OK, the interceptor has been configured. Just mark @ RepeatSubmit on the interface to be intercepted, as follows:
@RestController @RequestMapping("/user") //Annotated with @ RepeatSubmit annotation, all interfaces need to be intercepted @RepeatSubmit public class LoginController { @RequestMapping("/login") public String login(){ return "login success"; } }
At this time, request the URI:http://localhost:8080/springboot-demo/user/login can only be requested once within 5 seconds.
Note: the timeout time marked on the method will cover the time on the class because of 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.
demo details Source code: GitHub - wufaqidong / springboot intercept: Custom interceptor