SpringBoot攔截器以及源碼詳析
1、攔截器是什么
java里的攔截器(Interceptor)是動態(tài)攔截Action調用的對象,它提供了一種機制可以使開發(fā)者在一個Action執(zhí)行的前后執(zhí)行一段代碼,也可以在一個Action執(zhí)行前阻止其執(zhí)行,同時也提供了一種可以提取Action中可重用部分代碼的方式。在AOP中,攔截器用于在某個方法或者字段被訪問之前進行攔截,然后再之前或者之后加入某些操作。
上面的Action一般指的就是我們Controller層的接口。
2、自定義攔截器
一般自定義一個攔截器分為三步
(1)編寫一個攔截器實現(xiàn)HandlerInterceptor接口。
(2)攔截器注冊到容器中。
(3)配置攔截規(guī)則。
2.1 編寫攔截器
我們新建一個SpringBoot項目,然后自定義一個攔截器LoginInterceptor,攔截未登錄狀態(tài)下的某些請求。JDK1.8開始,接口的方法加上default關鍵字可以有默認實現(xiàn),所以實現(xiàn)一個接口只需要實現(xiàn)沒有加該關鍵字的方法。
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 { /** * 目標方法執(zhí)行之前執(zhí)行 * @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; } /** * 目標方法完成以后執(zhí)行 * @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執(zhí)行"); } /** * 頁面渲染以后執(zhí)行 * @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執(zhí)行"); } }
2.2 注冊和配置攔截器
在SpringBoot中,我們需要自定義配置的時候,只需要實現(xiàn)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/**"); // 不攔截這些路徑 } }
注意如果我們配置了攔截所有的路徑,那么一定要排除掉靜態(tài)資源,不然圖片樣式都會被攔截。
通過上面幾步,我們就實現(xiàn)了一個給系統(tǒng)加了一個攔截器。啟動驗證即可。
3、攔截器原理
我們通過打斷點調試的方法,看看從瀏覽器請求開始到后端是如何進行處理的。在DispatcherServlet的doDispatch方法打上斷點,這是請求的入口,瀏覽器發(fā)送請求后,由此方法進行轉發(fā)和處理。
debug模式啟動應用,訪問任意接口,跟蹤代碼流程
3.1 找到可以處理請求的handler以及handler的所有攔截器
這里找到了HandlerExecutionChain以及攔截器鏈,里面有三個攔截器,我們自定義的LoginInterceptor
和系統(tǒng)默認的兩個攔截器。
3.2 執(zhí)行攔截器的preHandle方法
在doDispatch方法中,有下面兩行代碼
// 執(zhí)行攔截器的preHandle方法,如果返回為fasle,則直接return,不執(zhí)行目標方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 反射執(zhí)行目標方法 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); // 執(zhí)行當前攔截器的preHandle方法 if (!interceptor.preHandle(request, response, this.handler)) { // 如果preHandle方法返回為false,則執(zhí)行當前攔截器的afterCompletion方法 triggerAfterCompletion(request, response, null); return false; } // 記錄當前攔截器的下標 this.interceptorIndex = i; } return true; }
通過上面的代碼, 我們知道如果當前攔截器的preHandle方法返回為true,則會繼續(xù)執(zhí)行下一攔截器的preHandle方法,否則執(zhí)行攔截器的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 { // 執(zhí)行當前攔截器的afterCompletion方法 interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } }
通過上面的代碼,我們知道對于攔截器的afterCompletion方法,是反向執(zhí)行的。
3.3 執(zhí)行目標方法
如果上面攔截器的所有preHandle方法返回都為true,那么在doDispatch方法內就不會直接return,而是繼續(xù)往下執(zhí)行目標方法。如果任何一個攔截器的preHandle方法返回為false,那么執(zhí)行完攔截器(已經(jīng)執(zhí)行過preHandle方法的攔截器)的afterCompletion方法后,在doDispatch方法內會直接return,不會執(zhí)行目標方法。
通過下面的代碼執(zhí)行目標方法
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
內部具體怎么執(zhí)行的就不看了,看看執(zhí)行完后的邏輯。
3.4 執(zhí)行攔截器的postHandle方法
目標方法執(zhí)行完后,代碼往下走
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); // 執(zhí)行當前攔截器的postHandle方法 interceptor.postHandle(request, response, this.handler, mv); } }
倒序執(zhí)行攔截器的postHandle方法
3.5 執(zhí)行攔截器的afterCompletion方法
繼續(xù)往下走
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
進入該方法,該方法會處理執(zhí)行結果,渲染頁面,該方法的最后,執(zhí)行下面的代碼
3.6 異常處理
如果在執(zhí)行doDispatch方法過程中,拋出了異常,在catch模塊,都會觸發(fā)執(zhí)行afterCompletion方法
4、總結
上面的過程,大概可以總結為以下幾步:
(1)根據(jù)當前請求,找到可以處理請求的handler和handler的所有攔截器。
(2)順序執(zhí)行所有攔截器的preHandle方法
如果當前攔截器的preHandle方法返回為true,則執(zhí)行下一攔截器的preHandle方法。如果當前攔截器返回為false,倒序執(zhí)行所有已經(jīng)執(zhí)行了的攔截器的afterCompletion。
(3)如果任何一個攔截器返回false,執(zhí)行返回,不執(zhí)行目標方法。
(4)所有攔截器都返回true,執(zhí)行目標方法。
(5)倒序執(zhí)行所有攔截器的postHandle方法。
(6)前面的步驟有任何異常都會觸發(fā)倒序執(zhí)行afterCompletion方法。
(7)頁面成功渲染后,也會倒序執(zhí)行afterCompletion方法。
到此這篇關于SpringBoot攔截器以及源碼的文章就介紹到這了,更多相關SpringBoot攔截器內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Springboot整合mybatisplus時,使用條件構造器排序報錯問題及解決
這篇文章主要介紹了Springboot整合mybatisplus時,使用條件構造器排序報錯問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04詳解如何獨立使用ribbon實現(xiàn)業(yè)務客戶端負載均衡
這篇文章主要為大家介紹了詳解如何獨立使用ribbon實現(xiàn)業(yè)務客戶端負載均衡,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06Java如何替換RequestBody和RequestParam參數(shù)的屬性
近期由于接手的老項目中存在所有接口中新增一個加密串來給接口做一個加密效果,所以就研究了一下Http請求鏈路,發(fā)現(xiàn)可以通過?javax.servlet.Filter去實現(xiàn),這篇文章主要介紹了Java替換RequestBody和RequestParam參數(shù)的屬性,需要的朋友可以參考下2023-10-10SpringCloud Gateway使用redis實現(xiàn)動態(tài)路由的方法
這篇文章主要介紹了SpringCloud Gateway使用redis實現(xiàn)動態(tài)路由的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01Spring Security賬戶與密碼驗證實現(xiàn)過程
這篇文章主要介紹了Spring Security賬戶與密碼驗證實現(xiàn)過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-03-03