搜尋
首頁Javajava教程SpringBoot攔截器原始碼分析

SpringBoot攔截器原始碼分析

May 15, 2023 pm 12:28 PM
springboot

1、攔截器是什麼

java裡的攔截器(Interceptor)是動態攔截Action呼叫的對象,它提供了一個機制可以讓開發者在一個Action執行的前後執行一段程式碼,也可以在一個Action執行前阻止其執行,同時也提供了一種可以提取Action中可重複使用部分程式碼的方式。在AOP中,攔截器用於在某個方法或欄位被存取之前進行攔截,然後再之前或之後加入某些操作。

上面的Action一般指的就是我們Controller層的介面。

2、自訂攔截器

一般自訂一個攔截器分成三步驟

(1)寫一個攔截器實作HandlerInterceptor介面。

(2)攔截器註冊到容器中。

(3)配置攔截規則。

2.1 寫攔截器

我們新建一個SpringBoot項目,然後自訂一個攔截器LoginInterceptor,攔截未登入狀態下的某些請求。 JDK1.8開始,介面的方法加上default關鍵字可以有預設實現,所以實作一個介面只需要實作沒有加該關鍵字的方法。

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 註冊和設定攔截器

在SpringBoot中,我們需要自訂設定的時候,只需要實作WebMvcConfigurer類別重寫對應的方法即可。這裡我們需要設定攔截器,那麼重寫它的addInterceptors方法即可。

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/**");  // 不拦截这些路径
    }
}

注意如果我們設定了攔截所有的路徑,那麼一定要排除掉靜態資源,不然圖片樣式都會被攔截。

透過上面幾步,我們就實作了一個為系統加了一個攔截器。啟動驗證即可。

3、攔截器原則

我們透過打斷點調試的方法,看看從瀏覽器請求開始到後端是如何進行處理的。在DispatcherServlet的doDispatch方法打上斷點,這是請求的入口,瀏覽器發送請求後,由此方法進行轉發和處理。

SpringBoot攔截器原始碼分析

debug模式啟動應用,存取任意接口,追蹤程式碼流程

3.1 找到可以處理請求的handler以及handler的所有攔截器

SpringBoot攔截器原始碼分析

這裡找到了HandlerExecutionChain以及攔截器鏈,裡面有三個攔截器,我們自訂的LoginInterceptor和系統預設的兩個攔截器。

3.2 執行攔截器的preHandle方法

在doDispatch方法中,有下面兩行程式碼

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

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

我們進入applyPreHandle方法,看看該方法的邏輯

/**
 * 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;
}

透過上面的程式碼, 我們知道如果目前攔截器的preHandle方法傳回為true,則會繼續執行下一攔截器的preHandle方法,否則執行攔截器的afterCompletion方法。

那我們來看看triggerAfterCompletion方法的邏輯。

/**
 * 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);
        }
    }
}

透過上面的程式碼,我們知道對於攔截器的afterCompletion方法,是反向執行的。

3.3 執行目標方法

如果上面攔截器的所有preHandle方法回傳都為true,那麼在doDispatch方法內就不會直接return,而是繼續往下執行目標方法。如果任何一個攔截器的preHandle方法回傳為false,那麼執行完攔截器(已經執行過preHandle方法的攔截器)的afterCompletion方法後,在doDispatch方法內會直接return,不會執行目標方法。

透過下面的程式碼執行目標方法

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

內部具體怎麼執行的就不看了,看看執行完後的邏輯。

3.4 執行攔截器的postHandle方法

目標方法執行完後,程式碼往下走

mappedHandler.applyPostHandle(processedRequest, response, mv);

查看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);
    }
}

倒序執行攔截器的postHandle方法

3.5 執行攔截器的afterCompletion方法

#繼續往下走

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

進入該方法,該方法會處理執行結果,渲染頁面,該方法的最後,執行下面的程式碼

SpringBoot攔截器原始碼分析

3.6 例外處理

如果在執行doDispatch方法過程中,拋出了例外,在catch模組,都會觸發執行afterCompletion方法

SpringBoot攔截器原始碼分析

以上是SpringBoot攔截器原始碼分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:亿速云。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)