How to register interceptors in spring MVC? What is the order in which multiple registered interceptors are executed? Combined with spring MVC source code, the interceptor is analyzed

Posted by slaterino on Fri, 01 Oct 2021 03:16:04 +0200

As the title says, this paper mainly introduces two parts.

  1. How to register interceptors in spring MVC? What is the order of execution of multiple interceptors?
  2. Combined with spring MVC source code, the interceptor is analyzed

How to register interceptors in spring MVC

First create an interceptor and then register it in spring MVC.

The first step is to create an interceptor. Spring MVC provides two methods,

  1. Implement interface HandlerInterceptor
  2. Inheriting abstract class HandlerInterceptorAdapter

2 is actually based on 1 and has been deprecated

Let's create an Interceptor Based on the first method.
Simulate a requirement and customize the Log annotation. If the annotation is used in the method, the Log will be recorded, and if there is no annotation, the Log will not be recorded
You need to define an annotation @ Log to act on the method. Interceptors can only intercept annotations on methods

/**
 * @author yujiaxing
 * @date 2021/09/28
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

Define interceptor

/**
 * @author yujiaxing
 * @date 2021/09/28
 */
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, java.lang.Object handler) throws Exception {
        log.info("handler type : {}", handler.getClass());  // handler type : class org.springframework.web.method.HandlerMethod
        Log logAnnotation = getLogAnnotation(handler);
        if (Objects.nonNull(logAnnotation)) {
            log.info("[[log information]: preHandle  {}", logAnnotation.value());
            log.info("[[request information], URL: {} ,Remote Host : {}", request.getRequestURI(), request.getRemoteHost());
        }
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, java.lang.Object handler, ModelAndView modelAndView) throws Exception {
        Log logAnnotation = getLogAnnotation(handler);
        log.info("[[log information] postHandle : {}", logAnnotation.value());
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, java.lang.Object handler, Exception ex) throws Exception {
        Log logAnnotation = getLogAnnotation(handler);
        log.info("[[log information] afterCompletion : {}", logAnnotation.value());
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    private Log getLogAnnotation(Object handler) {
        // HandlerMethod provides convenient access to method parameters, method return values, method comments, etc.
        if (handler instanceof HandlerMethod) {
            HandlerMethod methodHandle = (HandlerMethod) handler;
            Method method = methodHandle.getMethod();
            Log annotation = method.getAnnotation(Log.class);
            if (Objects.nonNull(annotation)) {
                return annotation;
            }
            return null;
        }
        return null;
    }
}


Register interceptors in spring MVC
There are two ways to do this:

  1. Implement interface WebMvcConfigurer
  2. Inheriting the abstract class WebMvcConfigurerAdapter

2 is also implemented on the basis of 1. And it was abandoned

Here, the registration interceptor is implemented based on the interface WebMvcConfigurer

/**
 * @author yujiaxing
 * @date 2021/09/28
 */
@Configuration
public class InterceptorRegisterConfig implements WebMvcConfigurer {

    @Autowired
    LogInterceptor logInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // A matching path is required
        List<String> includePatterns = new ArrayList<>();
        includePatterns.add("/api/test");
        includePatterns.add("/api/test/include");
        // Path to exclude
        List<String> excludePatterns = new ArrayList<>();
        excludePatterns.add("/api/test/exclude");
        // Register interceptor
        registry.addInterceptor(logInterceptor)
                .addPathPatterns(includePatterns)
                .excludePathPatterns(excludePatterns);
    }
}

Define a Controller to detect the interceptor.

/**
 * @author yujiaxing
 * @date 2021/09/28
 */
@RestController
public class TestController {

    @Log("Test log information")
    @GetMapping("/api/test")
    public String test() {
        return "this is /api/test";
    }

    @Log("Test log information---Test matching path")
    @GetMapping("/api/test/include")
    public String testInclude() {
        return "this is /api/test/include";
    }

    @Log("Test log information---Test mismatch path")
    @GetMapping("/api/test/exclude")
    public String testExclude() {
        return "this is /api/test/exclude";
    }
}

Access address: http://localhost:8081/api/test
browser:

View console:

visit: http://localhost:8081/api/test/include Similar to above

But visit http://localhost:8081/api/test/exclude , the relevant log information will not be printed.

This is because when registering with Spring MVC, the interceptor configures the paths that can match and exclude matching paths. In this way, our requirements are met. Only under a specific URL, the method with @ Log annotation will be intercepted by the Log interceptor for logging. Similar login, authentication can also be implemented in a similar way.

So how do multiple interceptors define the execution order of interceptors?

Add an interceptor LogInterceptorV2 in the

/**
 * @author yujiaxing
 * @date 2021/09/28
 */
@Slf4j
@Component
public class LogInterceptorV2 implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("handler type : {}", handler.getClass());  // handler type : class org.springframework.web.method.HandlerMethod
        Log logAnnotation = getLogAnnotation(handler);
        if (Objects.nonNull(logAnnotation)) {
            log.info("[log information V2]: preHandle  {}", logAnnotation.value());
            log.info("[[request information], URL: {} ,Remote Host : {}", request.getRequestURI(), request.getRemoteHost());
        }
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        Log logAnnotation = getLogAnnotation(handler);
        log.info("[log information V2]postHandle : {}", logAnnotation.value());
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        Log logAnnotation = getLogAnnotation(handler);
        log.info("[log information V2]afterCompletion : {}", logAnnotation.value());
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    private Log getLogAnnotation(Object handler) {
        // HandlerMethod provides convenient access to method parameters, method return values, method comments, etc.
        if (handler instanceof HandlerMethod) {
            HandlerMethod methodHandle = (HandlerMethod) handler;
            Method method = methodHandle.getMethod();
            Log annotation = method.getAnnotation(Log.class);
            if (Objects.nonNull(annotation)) {
                return annotation;
            }
            return null;
        }
        return null;
    }
}

Register in spring MVC and modify InterceptorRegisterConfig

@Configuration
public class InterceptorRegisterConfig implements WebMvcConfigurer {

    @Autowired
    LogInterceptor logInterceptor;
    @Autowired
    LogInterceptorV2 logInterceptorV2;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // A matching path is required
        List<String> includePatterns = new ArrayList<>();
        includePatterns.add("/api/test");
        includePatterns.add("/api/test/include");
        // Path to exclude
        List<String> excludePatterns = new ArrayList<>();
        excludePatterns.add("/api/test/exclude");
        // Register interceptor


        registry.addInterceptor(logInterceptorV2)
                .addPathPatterns(includePatterns)
                .excludePathPatterns(excludePatterns);

        registry.addInterceptor(logInterceptor)
                .addPathPatterns(includePatterns)
                .excludePathPatterns(excludePatterns);
    }

}

This is related to the order in which interceptors are registered in spring MVC. Because the register InterceptorRegistry maintains a List, interceptor access is orderly, with the first registered first and the second registered later.


When an interceptor, the order of execution is: prehandle -- > posthandle -- > aftercompletion

In case of multiple interceptors: interceptor V1, interceptor V2
V1.preHandle ------> V2.preHandle ------> V2.postHandle------>V1.postHandle------>V2.afterCompletion------>V1.afterCompletion

Combined with spring MVC source code, the interceptor is analyzed

First, let's look at the HandlerInterceptor interface

Combined with this figure: interceptors are used to intercept in steps 2 and 3.

Three methods:

  • preHandle: the intercept point before executing the handler. After HandlerMapping determines the appropriate handler object, it is called before the HandlerAdapter calls the handler.

  • postHandle: intercept point after successful execution of handler. Called after the HandlerAdapter actually invokes the handler, but before the DispatcherServlet render the view.

  • After completion: callback after request processing is completed, that is, after rendering the view. Note: only when the preHandle method of this interceptor completes successfully and returns true will it be called!

Let's take a look at the calling process of the source code: restart the project. When calling for the first time, the following figure will appear first
Combined with the results printed before our pursuit:
If there is no debug log information, add the log level of the web package in the application.properties configuration file

logging.level.web=debug

  1. The handler object mapped to is printed in RequestMappingHandlerMapping. Before executing the preHandle method, and after HandlerMapping determines the appropriate handler object.
    Let's take a look at the RequestMappingHandlerMapping class, which implements HandlerMapping, so the preHandle is indeed after HandlerMapping determines the appropriate handler object.
  2. The RequestResponseBodyMethodProcessor class prints out the parameters and return values. Refer to the following figure for parameter parsing and method return value processing, indicating that this is a handler object that calls the mapping. It is also proved that the preHandle method is called before HandlerAdapter calls the handler.

    The 3.postHandle method is called after the handler is called.
    4.afterCompletion is called after the postHandle method.

Let's take a look at how handlerexecution chain is returned when HandlerMapping receives a request in the above figure.

HandlerMapping#getHandler, which calls the HandlerMapping#getHandler method in DispatcherServlet#getHandler.

DispatcherServlet#getHandler


HandlerMapping#getHandler

AbstractHandlerMapping implements HandlerMapping and overrides the getHandler method, while RequestMappingHandlerMapping inherits AbstractHandlerMapping. The relationship between the three is as follows

AbstractHandlerMapping#getHandler

RequestMappingHandlerMapping#getHandler

So the console can see

This is the process of an HttpServletRequest request to HandlerMapping. A HandlerExecutionChain is returned, which contains the interceptors matched by the request. Those not matched have been filtered

interceptorList is the list of interceptors to be executed by the handler. The next step is to execute the interceptor, traverse the interceptor list and execute the interceptor.
In the HandlerExecutionChain#applyPreHandle, the following HandlerExecutionChain#applyPostHandle and HandlerExecutionChain#triggerAfterCompletion are the execution of postHandle and afterCompletion in the interceptor.

This is the analysis of interceptor pattern source code in Spring MVC.
When a request arrives at DispatcherServlet, call DispatcherServlet#getHandler. In HandlerMapping#getHandler, get a HandlerExecutionChain from RequestMappingHandlerMapping#getHandler, and then execute HandlerExecutionChain#applyPreHandle, and HandlerInterceptor#preHandle, To HandlerExecutionChain#applyPostHandle, HandlerInterceptor#postHandle, HandlerExecutionChain#triggerAfterCompletion
Go to HandlerInterceptor#afterCompletion in.

Topics: Spring MVC interceptor