Spring MVC 中整合攔截器Interceptor
目錄
WebRequestInterceptorAdapter 抽象類
AsyncWebRequestInterceptor 介面
Interceptor 簡介
關於Interceptor的一些簡介以及概念,可以直接訪問上一篇文章:https://blog.csdn.net/Soinice/article/details/83341457
Interceptor 實現方法
在 Spring MVC中,咱們要想實現攔截器的功能,主要通過兩種途徑,
第一種是實現HandlerInterceptor
介面,
第二種是實現WebRequestInterceptor
介面。
接下來,咱們分別詳細的介紹兩者的實現方法。
HandlerInterceptor 介面
HandlerInterceptor 介面
在HandlerInterceptor介面中,定義了 3 個方法,分別為preHandle()、postHandle()和afterCompletion(),咱們就是通過複寫這 3 個方法來對使用者的請求進行攔截處理的。因此,咱們可以通過直接實現HandlerInterceptor介面來實現攔截器的功能。
在實際應用中,咱們一般都是通過實現HandlerInterceptor介面或者繼承HandlerInterceptorAdapter抽象類,複寫preHandle()、postHandle()和afterCompletion()這 3 個方法來對使用者的請求進行攔截處理的。
下面,咱們就詳細介紹這個 3 個方法。
- preHandle(HttpServletRequest request, HttpServletResponse response, Object handle)方法,該方法在請求處理之前進行呼叫。SpringMVC 中的 Interceptor 是鏈式呼叫的,在一個應用中或者說是在一個請求中可以同時存在多個 Interceptor 。每個 Interceptor 的呼叫會依據它的宣告順序依次執行,而且最先執行的都是 Interceptor 中的 preHandle 方法,所以可以在這個方法中進行一些前置初始化操作或者是對當前請求做一個預處理,也可以在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布林值 Boolean 型別的,當它返回為 false 時,表示請求結束,後續的 Interceptor 和 Controller 都不會再執行;當返回值為 true 時,就會繼續呼叫下一個 Interceptor 的 preHandle 方法,如果已經是最後一個 Interceptor 的時候,就會是呼叫當前請求的 Controller 中的方法。
- postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)方法,通過 preHandle 方法的解釋咱們知道這個方法包括後面要說到的 afterCompletion 方法都只能在當前所屬的 Interceptor 的 preHandle 方法的返回值為 true 的時候,才能被呼叫。postHandle 方法在當前請求進行處理之後,也就是在 Controller 中的方法呼叫之後執行,但是它會在 DispatcherServlet 進行檢視返回渲染之前被呼叫,所以咱們可以在這個方法中對 Controller 處理之後的 ModelAndView 物件進行操作。postHandle 方法被呼叫的方向跟 preHandle 是相反的,也就是說,先宣告的 Interceptor 的 postHandle 方法反而會後執行。這和 Struts2 裡面的 Interceptor 的執行過程有點型別,Struts2 裡面的 Interceptor 的執行過程也是鏈式的,只是在 Struts2 裡面需要手動呼叫 ActionInvocation 的 invoke 方法來觸發對下一個 Interceptor 或者是 action 的呼叫,然後每一個 Interceptor 中在 invoke 方法呼叫之前的內容都是按照宣告順序執行的,而 invoke 方法之後的內容就是反向的。
- afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)方法,也是需要當前對應的 Interceptor 的 preHandle 方法的返回值為 true 時才會執行。因此,該方法將在整個請求結束之後,也就是在 DispatcherServlet 渲染了對應的檢視之後執行,這個方法的主要作用是用於進行資源清理的工作。
不過在 Spring 框架之中,其還提供了另外一個介面和一個抽象類,實現了對HandlerInterceptor介面的功能擴充套件,分別為:AsyncHandlerInterceptor介面和HandlerInterceptorAdapter抽象類.
AsyncHandlerInterceptor 介面
對於AsyncHandlerInterceptor
介面,其在繼承HandlerInterceptor
介面的同時,又聲明瞭一個新的方法afterConcurrentHandlingStarted()
HandlerInterceptorAdapter
抽象類
而HandlerInterceptorAdapter
抽象類,則是更進一步,在其繼承AsyncHandlerInterceptor
介面的同時,又複寫了preHandle
方法。因此,AsyncHandlerInterceptor
更像是一個過渡的介面。
WebRequestInterceptor 介面
WebRequestInterceptor 介面
在WebRequestInterceptor介面中也定義了 3 個方法,同HandlerInterceptor介面完全相同,咱們也是通過複寫這 3 個方法來使用者的請求進行攔截處理的。而且這 3 個方法都傳遞了同一個引數 WebRequest,那麼這個 WebRequest 到底是什麼呢?其實這個 WebRequest 是 Spring 中定義的一個介面,它裡面的方法定義跟 HttpServletRequest 類似,在WebRequestInterceptor中對 WebRequest 進行的所有操作都將同步到 HttpServletRequest 中,然後在當前請求中依次傳遞。
接下來,咱們主要講一下WebRequestInterceptor介面的 3 個函式:
- preHandle(WebRequest request)方法,該方法在請求處理之前進行呼叫,也就是說,其會在 Controller 中的方法呼叫之前被呼叫。這個方法跟 HandlerInterceptor 中的 preHandle 不同,主要區別在於該方法的返回值是void 型別的,也就是沒有返回值,因此我們主要用它來進行資源的準備工作,比如我們在使用 Hibernate 的時候,可以在這個方法中準備一個 Hibernate 的Session 物件,然後利用 WebRequest 的 setAttribute(name, value, scope) 把它放到 WebRequest 的屬性中。在這裡,進一步說說 setAttribute 方法的第三個引數 scope ,該引數是一個Integer 型別的。在 WebRequest 的父層介面 RequestAttributes 中對它定義了三個常量,分別為:
- SCOPE_REQUEST ,它的值是 0,表示只有在 request 中可以訪問。
- SCOPE_SESSION,它的值是1,如果環境允許的話,它表示的是一個區域性的隔離的 session,否則就代表普通的 session,並且在該 session 範圍內可以訪問。
- SCOPE_GLOBAL_SESSION,它的值是 2,如果環境允許的話,它表示的是一個全域性共享的 session,否則就代表普通的 session,並且在該 session 範圍內可以訪問。
- postHandle(WebRequest request, ModelMap model)方法,該方法在請求處理之後,也就是在 Controller 中的方法呼叫之後被呼叫,但是會在檢視返回被渲染之前被呼叫,所以可以在這個方法裡面通過改變資料模型 ModelMap 來改變資料的展示。該方法有兩個引數,WebRequest 物件是用於傳遞整個請求資料的,比如在 preHandle 中準備的資料都可以通過 WebRequest 來傳遞和訪問;ModelMap 就是 Controller 處理之後返回的 Model 物件,咱們可以通過改變它的屬性來改變返回的 Model 模型。
- afterCompletion(WebRequest request, Exception ex)方法,該方法會在整個請求處理完成,也就是在檢視返回並被渲染之後執行。因此可以在該方法中進行資源的釋放操作。而 WebRequest 引數就可以把咱們在 preHandle 中準備的資源傳遞到這裡進行釋放。Exception 引數表示的是當前請求的異常物件,如果在 Controller 中丟擲的異常已經被 Spring 的異常處理器給處理了的話,那麼這個異常物件就是是 null
接下來,咱們在看看以上介面和抽象類的具體程式碼:
WebRequestInterceptorAdapter 抽象類
在 Spring 框架之中,還提供了一個和WebRequestInterceptor介面長的很像的抽象類,那就是:WebRequestInterceptorAdapter,其實現了AsyncHandlerInterceptor介面,並在內部呼叫了WebRequestInterceptor介面。
AsyncWebRequestInterceptor 介面
AbstractInterceptor 抽象類
除了上面所講的內容,咱們還可以通過繼承 Struts2 框架提供的AbstractInterceptor抽象類來實現攔截的功能。如果咱們在深入一點研究,會發現AbstractInterceptor實現了Interceptor介面,而Interceptor介面又繼承了Serializable介面。
可以點選連結檢視上一篇文章:https://blog.csdn.net/Soinice/article/details/83341457
配置攔截器
在前面,咱們用了很大篇幅的內容講述了攔截器如何實現,因此,我相信大家實現攔截器已經沒有問題啦!接下來,咱們在看看,如何在 XML 檔案中配置攔截器,使咱們的攔截器生效。
在配置攔截器之前,有 4 個名稱的概念需要大家先了解一下,分別為:Join Point、Pointcut、Advice和Advisor.
- Join Point,表示“連線點”,它是程式執行中的某個階段點,比如方法的呼叫、異常的丟擲等;
- Advice,表示“通知”,它是某個連線點所採用的處理邏輯,也就是向連線點注入的程式碼;
- Pointcut,表示“切入點”,它是“連線點”的集合,是程式中需要注入 Advice 的位置的集合,指明 Advice 要在什麼樣的條件下才能被觸發;
- Advisor,它是 Pointcut 和 Advice 的配置器,包括 Pointcut 和 Advice,是將 Advice 注入程式中 Pointcut 位置的程式碼。
接下來,給出 XML 配置檔案的宣告:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
在 XML 檔案的頭部宣告完之後,咱們就可以在 Spring 的配置檔案中就可以使用mvc
標籤啦!而在mvc
標籤中有一個名為mvc:interceptors
的標籤,該標籤就是用於宣告 Spring 攔截器的。下面,給出一個配置示例:
<mvc:interceptors>
<!-- 使用 bean 定義一個 Interceptor,直接定義在 mvc:interceptors 下面的 Interceptor 將攔截所有的請求 -->
<bean class="com.imenger.interceptor.WrongCodeInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/demo/hello.do"/>
<!-- 定義在 mvc:interceptor 下面的 Interceptor,表示對特定的請求進行攔截 -->
<bean class="com.imenger.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
在 Spring 的XML 配置檔案中,咱們可以通過mvc:interceptors
標籤宣告一系列的攔截器,例如:
<mvc:interceptors>
<bean class="com.hit.interceptor.ContextInterceptor"/>
<bean class="com.hit.interceptor.LoginInterceptor"/>
<bean class="com.hit.interceptor.WrongCodeInterceptor"/>
</mvc:interceptors>
如上所示,這些攔截器就夠成了一個攔截器鏈,或者稱之為攔截器棧。而這些攔截器的執行順序是按宣告的先後順序執行的,即:先宣告的攔截器先執行,後宣告的攔截器後執行。在mvc:interceptors標籤下宣告interceptor標籤主要有兩種方式:
- 直接定義一個 Interceptor 實現類的 bean 物件,使用這種方式宣告的 Interceptor 攔截器將會對所有的請求進行攔截;
- 使用mvc:interceptor標籤進行宣告,使用這種方式進行宣告的 Interceptor 可以通過mvc:mapping子標籤來定義需要進行攔截的請求路徑。
此外,由於攔截器是 AOP 程式設計思想的典型應用,也就意味著咱們可以“切”到具體的“面”進行某些操作。例如
<bean id="WrongCodeInterceptor" class="com.imenger.interceptor.WrongCodeInterceptor">
<property name="userName" value="user-module"></property>
</bean>
<bean id="loginInterceptor" class="com.imenger.interceptor.LoginInterceptor">
<property name="excludePackages">
<list>
<value>com.hit.user.exception</value>
<value>com.hit.order.exception</value>
</list>
</property>
</bean>
<aop:config>
<aop:advisor advice-ref="WrongCodeInterceptor" pointcut="execution(* com.hit.*.demo..*.*(..)) || execution(* com.imenger.*.demo..*.*(..)) " />
<aop:advisor advice-ref="loginInterceptor" pointcut="execution(* com.imenger.*.demo..*.*(..))" />
</aop:config>
如上所示,咱們實現了切入到“面”進行特定的攔截功能,其中pointcut表示“切入點”,advisor表示要注入到pointcut的程式碼。大家可能會對pointcut中的*符合有所疑惑,它是“萬用字元”,表示可以匹配該位置上的任何名稱。當然,如果咱們要想使用aop標籤,就得先在配置檔案中就得進行宣告啦!