SpringBoot中攔截器和動態(tài)代理的區(qū)別詳解
1.攔截器
攔截器(Interceptor)準(zhǔn)確來說在 Spring MVC 中的一個(gè)很重要的組件,用于攔截 Controller 的請求。它的主要作用有以下幾個(gè):
- 權(quán)限驗(yàn)證:驗(yàn)證用戶是否登錄、是否有權(quán)限訪問某個(gè)接口。
- 日志記錄:記錄請求信息的日志,如請求參數(shù),響應(yīng)信息等。
- 性能監(jiān)控:監(jiān)控系統(tǒng)的運(yùn)行性能,如慢查詢接口等。
- 通用行為:插入一些通用的行為,比如開發(fā)環(huán)境忽略某些請求。
典型的使用場景是身份認(rèn)證、授權(quán)檢查、請求日志記錄等。
1.1 攔截器實(shí)現(xiàn)
在 Spring Boot 中攔截器的實(shí)現(xiàn)分為兩步:
- 創(chuàng)建一個(gè)普通的攔截器,實(shí)現(xiàn) HandlerInterceptor 接口,并重寫接口中的相關(guān)方法。
- 將上一步創(chuàng)建的攔截器加入到 Spring Boot 的配置文件中,并配置攔截規(guī)則。
具體實(shí)現(xiàn)如下。
① 實(shí)現(xiàn)自定義攔截器
import?org.springframework.stereotype.Component; import?org.springframework.web.servlet.HandlerInterceptor; import?org.springframework.web.servlet.ModelAndView; import?javax.servlet.http.HttpServletRequest; import?javax.servlet.http.HttpServletResponse; @Component public?class?TestInterceptor?implements?HandlerInterceptor?{ ????@Override ????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?Exception?{ ????????System.out.println("攔截器:執(zhí)行 preHandle 方法。"); ????????return?true; ????} ????@Override ????public?void?postHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?ModelAndView?modelAndView)?throws?Exception?{ ????????System.out.println("攔截器:執(zhí)行 postHandle 方法。"); ????} ????@Override ????public?void?afterCompletion(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?Exception?ex)?throws?Exception?{ ????????System.out.println("攔截器:執(zhí)行 afterCompletion 方法。"); ????} }
其中:
- boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle):在請求方法執(zhí)行前被調(diào)用,也就是調(diào)用目標(biāo)方法之前被調(diào)用。比如我們在操作數(shù)據(jù)之前先要驗(yàn)證用戶的登錄信息,就可以在此方法中實(shí)現(xiàn),如果驗(yàn)證成功則返回 true,繼續(xù)執(zhí)行數(shù)據(jù)操作業(yè)務(wù);否則就返回 false,后續(xù)操作數(shù)據(jù)的業(yè)務(wù)就不會被執(zhí)行了。
- void postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView):調(diào)用請求方法之后執(zhí)行,但它會在 DispatcherServlet 進(jìn)行渲染視圖之前被執(zhí)行。
- void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex):會在整個(gè)請求結(jié)束之后再執(zhí)行,也就是在 DispatcherServlet 渲染了對應(yīng)的視圖之后再執(zhí)行。
② 配置攔截規(guī)則
然后,我們再將上面的攔截器注入到項(xiàng)目配置文件中,并設(shè)置相應(yīng)攔截規(guī)則,具體實(shí)現(xiàn)代碼如下:
import?org.springframework.beans.factory.annotation.Autowired; 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?AppConfig?implements?WebMvcConfigurer?{ ????//?注入攔截器 ????@Autowired ????private?TestInterceptor?testInterceptor; ????@Override ????public?void?addInterceptors(InterceptorRegistry?registry)?{ ????????registry.addInterceptor(testInterceptor)?//?添加攔截器 ????????????????.addPathPatterns("/**");?//?攔截所有地址 ??????????.excludePathPatterns("/login");?//?放行接口 ????} }
這樣我們的攔截器就實(shí)現(xiàn)完了。
1.2 攔截器實(shí)現(xiàn)原理
Spring Boot 攔截器是基于 Java 的 Servlet 規(guī)范實(shí)現(xiàn)的,通過實(shí)現(xiàn) HandlerInterceptor 接口來實(shí)現(xiàn)攔截器功能。
在 Spring Boot 框架的執(zhí)行流程中,攔截器被注冊在 DispatcherServlet 的 doDispatch() 方法中,該方法是 Spring Boot 框架的核心方法,用于處理請求和響應(yīng)。
程序每次執(zhí)行時(shí)都會調(diào)用 doDispatch() 方法時(shí),并驗(yàn)證攔截器(鏈),之后再根據(jù)攔截器返回的結(jié)果,進(jìn)行下一步的處理。如果返回的是 true,那么繼續(xù)調(diào)用目標(biāo)方法,反之則會直接返回驗(yàn)證失敗給前端。
doDispatch 源碼實(shí)現(xiàn)如下:
protected?void?doDispatch(HttpServletRequest?request,?HttpServletResponse?response)?throws?Exception?{ ????HttpServletRequest?processedRequest?=?request; ????HandlerExecutionChain?mappedHandler?=?null; ????boolean?multipartRequestParsed?=?false; ????WebAsyncManager?asyncManager?=?WebAsyncUtils.getAsyncManager(request); ????try?{ ????????try?{ ????????????ModelAndView?mv?=?null; ????????????Object?dispatchException?=?null; ????????????try?{ ????????????????processedRequest?=?this.checkMultipart(request); ????????????????multipartRequestParsed?=?processedRequest?!=?request; ????????????????mappedHandler?=?this.getHandler(processedRequest); ????????????????if?(mappedHandler?==?null)?{ ????????????????????this.noHandlerFound(processedRequest,?response); ????????????????????return; ????????????????} ????????????????HandlerAdapter?ha?=?this.getHandlerAdapter(mappedHandler.getHandler()); ????????????????String?method?=?request.getMethod(); ????????????????boolean?isGet?=?HttpMethod.GET.matches(method); ????????????????if?(isGet?||?HttpMethod.HEAD.matches(method))?{ ????????????????????long?lastModified?=?ha.getLastModified(request,?mappedHandler.getHandler()); ????????????????????if?((new?ServletWebRequest(request,?response)).checkNotModified(lastModified)?&&?isGet)?{ ????????????????????????return; ????????????????????} ????????????????} ????????????????//?調(diào)用預(yù)處理【重點(diǎn)】 ????????????????if?(!mappedHandler.applyPreHandle(processedRequest,?response))?{ ????????????????????return; ????????????????} ????????????????//?執(zhí)行?Controller?中的業(yè)務(wù) ????????????????mv?=?ha.handle(processedRequest,?response,?mappedHandler.getHandler()); ????????????????if?(asyncManager.isConcurrentHandlingStarted())?{ ????????????????????return; ????????????????} ????????????????this.applyDefaultViewName(processedRequest,?mv); ????????????????mappedHandler.applyPostHandle(processedRequest,?response,?mv); ????????????}?catch?(Exception?var20)?{ ????????????????dispatchException?=?var20; ????????????}?catch?(Throwable?var21)?{ ????????????????dispatchException?=?new?NestedServletException("Handler?dispatch?failed",?var21); ????????????} ????????????this.processDispatchResult(processedRequest,?response,?mappedHandler,?mv,?(Exception)dispatchException); ????????}?catch?(Exception?var22)?{ ????????????this.triggerAfterCompletion(processedRequest,?response,?mappedHandler,?var22); ????????}?catch?(Throwable?var23)?{ ????????????this.triggerAfterCompletion(processedRequest,?response,?mappedHandler,?new?NestedServletException("Handler?processing?failed",?var23)); ????????} ????}?finally?{ ????????if?(asyncManager.isConcurrentHandlingStarted())?{ ????????????if?(mappedHandler?!=?null)?{ ????????????????mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest,?response); ????????????} ????????}?else?if?(multipartRequestParsed)?{ ????????????this.cleanupMultipart(processedRequest); ????????} ????} }
從上述源碼可以看出在開始執(zhí)行 Controller 之前,會先調(diào)用 預(yù)處理方法 applyPreHandle,而 applyPreHandle 方法的實(shí)現(xiàn)源碼如下:
boolean?applyPreHandle(HttpServletRequest?request,?HttpServletResponse?response)?throws?Exception?{ ????for(int?i?=?0;?i?<?this.interceptorList.size();?this.interceptorIndex?=?i++)?{ ????????//?獲取項(xiàng)目中使用的攔截器?HandlerInterceptor ????????HandlerInterceptor?interceptor?=?(HandlerInterceptor)this.interceptorList.get(i); ????????if?(!interceptor.preHandle(request,?response,?this.handler))?{ ????????????this.triggerAfterCompletion(request,?response,?(Exception)null); ????????????return?false; ????????} ????} ????return?true; }
從上述源碼可以看出,在 applyPreHandle 中會獲取所有的攔截器 HandlerInterceptor 并執(zhí)行攔截器中的 preHandle 方法,這樣就會咱們前面定義的攔截器對應(yīng)上了,如下圖所示:
此時(shí)用戶登錄權(quán)限的驗(yàn)證方法就會執(zhí)行,這就是攔截器的執(zhí)行過程。因此,可以得出結(jié)論,攔截器的實(shí)現(xiàn)主要是依賴 Servlet 或 Spring 執(zhí)行流程來進(jìn)行攔截和功能增強(qiáng)的。
2.動態(tài)代理
動態(tài)代理是一種設(shè)計(jì)模式,它是指在運(yùn)行時(shí)提供代理對象,來擴(kuò)展目標(biāo)對象的功能。在 Spring 中的,動態(tài)代理的實(shí)現(xiàn)手段有以下兩種:
- JDK 動態(tài)代理:通過反射機(jī)制生成代理對象,目標(biāo)對象必須實(shí)現(xiàn)接口。
- CGLIB 動態(tài)代理:通過生成目標(biāo)類的子類來實(shí)現(xiàn)代理,不要求目標(biāo)對象實(shí)現(xiàn)接口。
動態(tài)代理的主要作用包括:
- 擴(kuò)展目標(biāo)對象的功能:如添加日志、驗(yàn)證參數(shù)等。
- 控制目標(biāo)對象的訪問:如進(jìn)行權(quán)限控制。
- 延遲加載目標(biāo)對象:在需要時(shí)才實(shí)例化目標(biāo)對象。
- 遠(yuǎn)程代理:將請求轉(zhuǎn)發(fā)到遠(yuǎn)程的目標(biāo)對象上。
3.攔截器 VS 動態(tài)代理
因此,我們可以得出結(jié)論,攔截器和動態(tài)代理雖然都是用來實(shí)現(xiàn)功能增強(qiáng)的,但二者完全不同,他們的主要區(qū)別體現(xiàn)在以下幾點(diǎn):
- 使用范圍不同:攔截器通常用于 Spring MVC 中,主要用于攔截 Controller 請求。動態(tài)代理可以使用在 Bean 中,主要用于提供 bean 的代理對象,實(shí)現(xiàn)對 bean 方法的攔截。
- 實(shí)現(xiàn)原理不同:攔截器是通過 HandlerInterceptor 接口來實(shí)現(xiàn)的,主要是通過 afterCompletion、postHandle、preHandle 這三個(gè)方法在請求前后進(jìn)行攔截處理。動態(tài)代理主要有 JDK 動態(tài)代理和 CGLIB 動態(tài)代理,JDK 通過反射生成代理類;CGLIB 通過生成被代理類的子類來實(shí)現(xiàn)代理。
- 加入時(shí)機(jī)不同:攔截器是在運(yùn)行階段動態(tài)加入的;動態(tài)代理是在編譯期或運(yùn)行期生成的代理類。
- 使用難易程度不同:攔截器相對簡單,通過實(shí)現(xiàn)接口即可使用。動態(tài)代理稍微復(fù)雜,需要了解動態(tài)代理的實(shí)現(xiàn)原理,然后通過相應(yīng)的 api 實(shí)現(xiàn)。
小結(jié)
在 Spring Boot 中,攔截器和動態(tài)代理都是用來實(shí)現(xiàn)功能增強(qiáng)的,但二者沒有任何關(guān)聯(lián)關(guān)系,它的區(qū)別主要體現(xiàn)在使用范圍、實(shí)現(xiàn)原理、加入時(shí)機(jī)和使用的難易程度都是不同的。
以上就是SpringBoot中攔截器和動態(tài)代理的區(qū)別詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot攔截器和動態(tài)代理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java設(shè)計(jì)模式之命令模式CommandPattern詳解
這篇文章主要介紹了Java設(shè)計(jì)模式之命令模式CommandPattern詳解,命令模式是把一個(gè)請求封裝為一個(gè)對象,從而使你可用不同的請求對客戶進(jìn)行參數(shù)化;對請求排隊(duì)或記錄請求日志,以及支持可撤銷的操作,需要的朋友可以參考下2023-10-10Java你不了解的大數(shù)型BigInteger與BigDecimal類
這篇文章主要介紹了Java 處理超大數(shù)類型之BigInteger與BigDecimal案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2022-05-05Spring Boot Admin的使用詳解(Actuator監(jiān)控接口)
這篇文章主要介紹了Spring Boot Admin的使用詳解(Actuator監(jiān)控接口),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05關(guān)于通過java調(diào)用datax,返回任務(wù)執(zhí)行的方法
今天小編就為大家分享一篇關(guān)于通過java調(diào)用datax,返回任務(wù)執(zhí)行的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08mybatis如何根據(jù)表逆向自動化生成代碼實(shí)例
逆向工程是一個(gè)專門為 MyBatis 框架使用者設(shè)計(jì)的代碼生成器,可以根據(jù)數(shù)據(jù)庫中的表字段名,自動生成 POJO 類,mapper 接口與 SQL 映射文件,這篇文章主要給大家介紹了關(guān)于mybatis如何根據(jù)表逆向自動化生成代碼的相關(guān)資料,需要的朋友可以參考下2021-08-08Java面試之限流的實(shí)現(xiàn)方式小結(jié)
限流是指在各種應(yīng)用場景中,通過技術(shù)和策略手段對數(shù)據(jù)流量、請求頻率或資源消耗進(jìn)行有計(jì)劃的限制,本文為大家整理了常見的限流的實(shí)現(xiàn)方式,有需要的可以參考下2024-02-02實(shí)例講解Java設(shè)計(jì)模式編程中如何運(yùn)用代理模式
這篇文章主要介紹了Java設(shè)計(jì)模式編程中如何運(yùn)用代理模式,文中舉了普通代理和強(qiáng)制代理的例子作為代理模式的擴(kuò)展內(nèi)容,需要的朋友可以參考下2016-02-02