Home >Java >javaTutorial >SpringBoot interceptor source code analysis

SpringBoot interceptor source code analysis

PHPz
PHPzforward
2023-05-15 12:28:231542browse

1. What is an interceptor?

The interceptor (Interceptor) in Java is an object that dynamically intercepts Action calls. It provides a mechanism that allows developers to execute a piece of code before and after an Action is executed. , can also prevent the execution of an Action before it is executed, and also provides a way to extract reusable parts of the code in the Action. In AOP, interceptors are used to intercept a method or field before it is accessed, and then add certain operations before or after.

The Action above generally refers to the interface of our Controller layer.

2. Customized interceptor

Generally, customizing an interceptor is divided into three steps

(1) Write an interceptor to implement the HandlerInterceptor interface.

(2) The interceptor is registered in the container.

(3) Configure interception rules.

2.1 Writing an interceptor

We create a new SpringBoot project, and then customize an interceptor LoginInterceptor to intercept certain requests in the non-logged-in state. Starting from JDK 1.8, interface methods with the default keyword can have default implementations, so to implement an interface you only need to implement the method without this 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;

/**
 * 登录拦截器
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 目标方法执行之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求路径
        String requestUrl = request.getRequestURI();
        log.info("请求的路径是: {}", requestUrl);

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

        request.setAttribute("msg", "请先登录");
        // 携带msg跳转到登录页
        request.getRequestDispatcher("/").forward(request, response);
        return false;
    }

    /**
     * 目标方法完成以后执行
     * @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执行");
    }

    /**
     * 页面渲染以后执行
     * @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执行");
    }
}

2.2 Registering and configuring interceptors

In SpringBoot, when we need to customize the configuration, we only need to implement the WebMvcConfigurer class and rewrite the corresponding method. Here we need to configure the interceptor, so just rewrite 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;

// 表示这是一个配置类
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  // 拦截所有路径
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");  // 不拦截这些路径
    }
}

Note that if we configure to intercept all paths, we must exclude static resources, otherwise the image styles will be intercepted.

Through the above steps, we have implemented an interceptor added to the system. Just start verification.

3. Interceptor Principle

We use the breakpoint debugging method to see how the browser request is processed from the beginning to the backend. Set a breakpoint in the doDispatch method of DispatcherServlet. This is the entry point for the request. After the browser sends the request, this method forwards and processes it.

SpringBoot interceptor source code analysis

Start the application in debug mode, access any interface, and track the code flow

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

SpringBoot interceptor source code analysis

Here we found the HandlerExecutionChain and the interceptor chain. There are three interceptors in it, our custom LoginInterceptor and the system's default two interceptors.

3.2 Execute the preHandle method of the interceptor

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

// 执行拦截器的preHandle方法,如果返回为fasle,则直接return,不执行目标方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

// 反射执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

We enter the applyPreHandle method to see 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 {
    // 遍历拦截器
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        // 执行当前拦截器的preHandle方法
        if (!interceptor.preHandle(request, response, this.handler)) {
            // 如果preHandle方法返回为false,则执行当前拦截器的afterCompletion方法
            triggerAfterCompletion(request, response, null);
            return false;
        }
        // 记录当前拦截器的下标
        this.interceptorIndex = i;
    }
    return true;
}

Through the above code, we know that if the preHandle method of the current interceptor returns true, the preHandle method of the next interceptor will continue to be executed, otherwise the afterCompletion method of the interceptor will be executed.

Then 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) {
    // 反向遍历拦截器
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            // 执行当前拦截器的afterCompletion方法
            interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

Through the above code, we know that the afterCompletion method of the interceptor is executed in reverse.

3.3 Execute the target method

If all preHandle methods of the above interceptor return true, then there will not be a direct return in the doDispatch method, but the target method will continue to be executed. If the preHandle method of any interceptor returns false, then after executing the afterCompletion method of the interceptor (the interceptor that has executed the preHandle method), the doDispatch method will directly return and the target method will not be executed.

Execute the target method through the following code

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

I won’t look at the specific internal execution, but look at the logic after execution.

3.4 Execute the postHandle method of the interceptor

After the target method is executed, the code goes down

mappedHandler.applyPostHandle(processedRequest, response, mv);

View the logic of applyPostHandle

/**
 * Apply postHandle methods of registered interceptors.
 */
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    throws Exception {
	// 反向遍历
    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        // 执行当前拦截器的postHandle方法
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

Execute the interception in reverse order The postHandle method of the interceptor

3.5 Execute the afterCompletion method of the interceptor

Continue going down

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

Enter this method, which will process the execution results and render the page. This method At the end, execute the following code

SpringBoot interceptor source code analysis

3.6 Exception handling

If an exception is thrown during the execution of the doDispatch method, it will be triggered in the catch module Execute afterCompletion method

SpringBoot interceptor source code analysis

The above is the detailed content of SpringBoot interceptor source code analysis. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete