What is an interceptor

In Java, an Interceptor is an object that intercepts Action calls dynamically. It provides a mechanism for developers to execute a piece of code before and after an Action is executed, and to block an Action before it is executed. It also provides a way to extract reusable portions of the Action code. In AOP, interceptors are used to intercept a method or field before it is accessed, and then add some action before or after.

The actions above generally refer to our Controller layer interface.

2. Custom interceptors

Customizing an interceptor generally consists of three steps

(1) Write an interceptor to implement HandlerInterceptor interface.

(2) The interceptor is registered with the container.

(3) Configure an interception rule.

2.1 Write interceptors

Let’s create a new SpringBoot project, and then define a custom LoginInterceptor that intercepts certain requests when not logged in. Starting with JDK1.8, interface methods with the default keyword can be implemented by default, so implementing an interface requires only implementing methods without the keyword.

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/** * login interceptor */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    /** * execute * before the target method executes@param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Get the request path
        String requestUrl = request.getRequestURI();
        log.info("The requested path is: {}", requestUrl);

        String username = request.getParameter("username");
        if(username ! =null) {
            / / release
            return true;
        }

        request.setAttribute("msg"."Please log in first.");
        // Jump to the login page with MSG
        request.getRequestDispatcher("/").forward(request, response);
        return false;
    }

    /** * execute * after the target method completes@param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("PostHandle execution");
    }

    /** * execute * after the page is rendered@param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("AfterCompletion execution"); }}Copy the code

2.2 Registering and configuring interceptors

In SpringBoot, when we need to customize the configuration, we just need to implement the WebMvcConfigurer class to override the corresponding method. Here we need to configure the interceptor, so override its addInterceptors method.

import com.codeliu.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

// indicates this is a configuration class
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/ * *")  // Intercept all paths
                .excludePathPatterns("/"."/login"."/css/**"."/fonts/**"."/images/**"."/js/**");  // Do not intercept these paths}}Copy the code

Note that if we are configured to block all paths, we must exclude static resources, otherwise the image styles will be blocked.

Through the above steps, we have implemented a system to add an interceptor. Start the validation.

3. Principle of interceptor

Let’s take a look at how the browser request is processed from the start to the back end by breaking point debugging. Breakpoints are made to the doDispatch method of the DispatcherServlet, which is the entry point to the request, which is forwarded and processed by the method once the browser sends the request.

Debug mode Starts applications, accesses any interface, and traces code flow

3.1 Find the handler that can handle the request and all interceptors for that handler

Here we find the HandlerExecutionChain and the interceptor chain, which has three interceptors, our custom LoginInterceptor and the system’s default two interceptors.

3.2 Executing the interceptor’s preHandle method

In the doDispatch method, there are the following two lines of code

// Execute the interceptor's preHandle method. If fasle is returned, return without executing the target method
if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
}

// reflection executes the target method
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
Copy the code

Let’s go to the applyPreHandle method and look at the logic of the method

/**
 * Apply preHandle methods of registered interceptors.
 * @return {@code true} if the execution chain should proceed with the
 * next interceptor or the handler itself. Else, DispatcherServlet assumes
 * that this interceptor has already dealt with the response itself.
 */
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Iterate over the interceptor
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        // Executes the preHandle method of the current interceptor
        if(! interceptor.preHandle(request, response,this.handler)) {
            // If the preHandle method returns false, the afterCompletion method of the current interceptor is executed
            triggerAfterCompletion(request, response, null);
            return false;
        }
        // Records the subscript of the current interceptor
        this.interceptorIndex = i;
    }
    return true;
}
Copy the code

From the code above, we know that if the current interceptor’s preHandle method returns true, execution of the next interceptor’s preHandle method continues, otherwise the interceptor’s afterCompletion method is executed.

So let’s look at the logic of the triggerAfterCompletion method.

/** * Trigger afterCompletion callbacks on the mapped HandlerInterceptors. * Will just invoke afterCompletion for all interceptors whose preHandle invocation * has successfully completed and returned true. */
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    // Reverse traverse the interceptor
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            // Execute the afterCompletion method of the current interceptor
            interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); }}}Copy the code

From the code above, we know that the afterCompletion method for the interceptor is executed in reverse.

3.3 Implement the target method

If all of the preHandle methods of the interceptor above return true, then there is no direct return within the doDispatch method, and the target method continues to execute. If the preHandle method of either interceptor returns false, then after executing the afterCompletion method of the interceptor (an interceptor that has already executed the preHandle method), it will return directly within the doDispatch method and will not execute the target method.

Execute the target method with the following code

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
Copy the code

We don’t have to look at the internal implementation, but look at the logic after implementation.

3.4 Execute the interceptor’s postHandle method

After the target method executes, the code moves down

mappedHandler.applyPostHandle(processedRequest, response, mv);
Copy the code

View the logic of applyPostHandle

/** * Apply postHandle methods of registered interceptors. */
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    throws Exception {
	// Reverse traversal
    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        // Executes the postHandle method of the current interceptor
        interceptor.postHandle(request, response, this.handler, mv); }}Copy the code

Executes the interceptor’s postHandle method in reverse order

3.5 Execute the afterCompletion method of the interceptor

Keep going down

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
Copy the code

Enter the method, which processes the result of execution, renders the page, and, at the end of the method, executes the following code

3.6 Exception Handling

If an exception is thrown during the execution of the doDispatch method, the afterCompletion method is triggered in the Catch module

4, summarize

The above process can be summarized as the following steps:

(1) Based on the current request, find the handler that can handle the request and all interceptors of the handler.

(2) Execute the preHandle method of all interceptors in sequence

  • If the current interceptorpreHandleMethod returnstrueTo execute the next interceptorpreHandleMethods.
  • If the current interceptor returns false, all executed interceptors are executed in reverse orderafterCompletion.

(3) If either interceptor returns false, the target method is not executed.

(4) All interceptors return true and execute the target method.

(5) Execute all interceptor postHandle methods in reverse order.

(6) Any exception to the previous steps will trigger the afterCompletion method to be executed in reverse order.

(7) After the page is successfully rendered, afterCompletion method will also be executed in reverse order.