昨天新接了一個須要,「攔截 XXX,而後 OOO」,好吧,說白了就是要用攔截器乾點事(實現一個具體的功能)。以前,也在網絡上搜了不少關於Interceptor
的文章,但感受內容都大同小異,並且知識點零零散散,不太方便閱讀。所以,正好藉此機會,整理一篇關於攔截器的文章,在此分享給你們,以供你們參考閱讀。java
Java 裏的攔截器是動態攔截 action 調用的對象。它提供了一種機制可使開發者能夠定義在一個 action 執行的先後執行的代碼,也能夠在一個 action 執行前阻止其執行,同時也提供了一種能夠提取 action 中可重用部分的方式。在AOP(Aspect-Oriented Programming,面向切面編程)中攔截器用於在某個方法或字段被訪問以前進行攔截,而後在以前或以後加入某些操做。web
攔截器 Interceptor 的攔截功能是基於 Java 的動態代理來實現的,具體能夠參考博文「 用 Java 實現攔截器 Interceptor 的攔截功能 」,也能夠經過閱讀 Spring 源代碼來了解更爲權威的實現細節。spring
在 Spring 框架之中,我們要想實現攔截器的功能,主要經過兩種途徑,第一種是實現HandlerInterceptor
接口,第二種是實現WebRequestInterceptor
接口。接下來,我們分別詳細的介紹二者的實現方法。apache
在HandlerInterceptor
接口中,定義了 3 個方法,分別爲preHandle()
、postHandle()
和afterCompletion()
,我們就是經過複寫這 3 個方法來對用戶的請求進行攔截處理的。所以,我們能夠經過直接實現HandlerInterceptor
接口來實現攔截器的功能。不過在 Spring 框架之中,其還提供了另一個接口和一個抽象類,實現了對HandlerInterceptor
接口的功能擴展,分別爲:AsyncHandlerInterceptor
和HandlerInterceptorAdapter
.編程
對於AsyncHandlerInterceptor
接口,其在繼承HandlerInterceptor
接口的同時,又聲明瞭一個新的方法afterConcurrentHandlingStarted()
;而HandlerInterceptorAdapter
抽象類,則是更進一步,在其繼承AsyncHandlerInterceptor
接口的同時,又複寫了preHandle
方法。所以,AsyncHandlerInterceptor
更像是一個過渡的接口。spring-mvc
在實際應用中,我們通常都是經過實現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 渲染了對應的視圖以後執行,這個方法的主要做用是用於進行資源清理的工做。接下來,我們在看看以上接口和抽象類的具體代碼:session
HandlerInterceptor
接口:mvc
package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public interface HandlerInterceptor { boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; }
AsyncHandlerInterceptor
接口:app
package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public interface AsyncHandlerInterceptor extends HandlerInterceptor { void afterConcurrentHandlingStarted( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; }
HandlerInterceptorAdapter
抽象類:
package org.springframework.web.servlet.handler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * Abstract adapter class for the HandlerInterceptor interface, * for simplified implementation of pre-only/post-only interceptors. * * @author Juergen Hoeller * @since 05.12.2003 */ public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { /** * This implementation always returns {@code true}. */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /** * This implementation is empty. */ public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * This implementation is empty. */ public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } /** * This implementation is empty. */ public void afterConcurrentHandlingStarted( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
如上面的代碼所示,其實在HandlerInterceptor
和AsyncHandlerInterceptor
中還有不少的代碼註釋,只是博主感受太多了,就將其所有刪除啦!若是你們對這些註釋感興趣的話,能夠自行查看源代碼。下面,我們以繼承HandlerInterceptorAdapter
抽象類爲例進行演示:
package com.hit.interceptor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author 維C果糖 * @create 2017-03-31 */ public class WrongCodeInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("WrongCodeInterceptor, preHandle......"); return true; } @Override public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("WrongCodeInterceptor, postHandle......"); } @Override public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("WrongCodeInterceptor, afterCompletion......"); } @Override public void afterConcurrentHandlingStarted( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("WrongCodeInterceptor, afterConcurrentHandlingStarted......"); } }
在WebRequestInterceptor
接口中也定義了 3 個方法,同HandlerInterceptor
接口徹底相同,我們也是經過複寫這 3 個方法來用戶的請求進行攔截處理的。並且這 3 個方法都傳遞了同一個參數 WebRequest,那麼這個 WebRequest 究竟是什麼呢?其實這個 WebRequest 是 Spring 中定義的一個接口,它裏面的方法定義跟 HttpServletRequest 相似,在WebRequestInterceptor
中對 WebRequest 進行的全部操做都將同步到 HttpServletRequest 中,而後在當前請求中依次傳遞。
在 Spring 框架之中,還提供了一個和WebRequestInterceptor
接口長的很像的抽象類,那就是:WebRequestInterceptorAdapter
,其實現了AsyncHandlerInterceptor
接口,並在內部調用了WebRequestInterceptor
接口。
接下來,我們主要講一下WebRequestInterceptor
接口的 3 個函數:
preHandle(WebRequest request)
方法,該方法在請求處理以前進行調用,也就是說,其會在 Controller 中的方法調用以前被調用。這個方法跟 HandlerInterceptor 中的 preHandle 不一樣,主要區別在於該方法的返回值是void 類型的,也就是沒有返回值,所以咱們主要用它來進行資源的準備工做,好比咱們在使用 Hibernate 的時候,能夠在這個方法中準備一個 Hibernate 的Session 對象,而後利用 WebRequest 的 setAttribute(name, value, scope) 把它放到 WebRequest 的屬性中。在這裏,進一步說說 setAttribute 方法的第三個參數 scope ,該參數是一個Integer 類型的。在 WebRequest 的父層接口 RequestAttributes 中對它定義了三個常量,分別爲:
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.接下來,我們在看看以上接口和抽象類的具體代碼:
WebRequestInterceptor
接口:
package org.springframework.web.context.request; import org.springframework.ui.ModelMap; public interface WebRequestInterceptor { void preHandle(WebRequest request) throws Exception; void postHandle(WebRequest request, ModelMap model) throws Exception; void afterCompletion(WebRequest request, Exception ex) throws Exception; }
WebRequestInterceptorAdapter
抽象類:
package org.springframework.web.servlet.handler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.util.Assert; import org.springframework.web.context.request.AsyncWebRequestInterceptor; import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * Adapter that implements the Servlet HandlerInterceptor interface * and wraps an underlying WebRequestInterceptor. * * @author Juergen Hoeller * @since 2.0 * @see org.springframework.web.context.request.WebRequestInterceptor * @see org.springframework.web.servlet.HandlerInterceptor */ public class WebRequestHandlerInterceptorAdapter implements AsyncHandlerInterceptor { private final WebRequestInterceptor requestInterceptor; /** * Create a new WebRequestHandlerInterceptorAdapter for the given WebRequestInterceptor. * @param requestInterceptor the WebRequestInterceptor to wrap */ public WebRequestHandlerInterceptorAdapter(WebRequestInterceptor requestInterceptor) { Assert.notNull(requestInterceptor, "WebRequestInterceptor must not be null"); this.requestInterceptor = requestInterceptor; } public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { this.requestInterceptor.preHandle(new DispatcherServletWebRequest(request, response)); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { this.requestInterceptor.postHandle(new DispatcherServletWebRequest(request, response), (modelAndView != null && !modelAndView.wasCleared() ? modelAndView.getModelMap() : null)); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { this.requestInterceptor.afterCompletion(new DispatcherServletWebRequest(request, response), ex); } public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) { if (this.requestInterceptor instanceof AsyncWebRequestInterceptor) { AsyncWebRequestInterceptor asyncInterceptor = (AsyncWebRequestInterceptor) this.requestInterceptor; DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response); asyncInterceptor.afterConcurrentHandlingStarted(webRequest); } } }
如上面的代碼所示,展現了WebRequestInterceptor
接口和WebRequestInterceptorAdapter
抽象類的源碼。下面,我們以實現WebRequestInterceptor
接口爲例進行演示:
package com.hit.interceptor; import org.springframework.ui.ModelMap; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequestInterceptor; /** * @author 維C果糖 * @create 2017-03-31 */ public class WrongCodeInterceptor implements WebRequestInterceptor { @Override public void preHandle(WebRequest request) throws Exception { System.out.println("WrongCodeInterceptor, preHandle......"); } @Override public void postHandle(WebRequest request, ModelMap model) throws Exception { System.out.println("WrongCodeInterceptor, postHandle......"); } @Override public void afterCompletion(WebRequest request, Exception ex) throws Exception { System.out.println("WrongCodeInterceptor, afterCompletion......"); } }
除了上面3.2
和3.3
所講的內容,我們還能夠經過繼承 Struts2
框架提供的AbstractInterceptor
抽象類來實現攔截的功能。若是我們在深刻一點研究,會發現AbstractInterceptor
實現了Interceptor
接口,而Interceptor
接口又繼承了Serializable
接口。
在Interceptor
接口中,提供了 3 個方法供我們使用,分別爲init()
、destroy()
和intercept()
,因爲AbstractInterceptor
實現了Interceptor
接口,所以我們就能夠直接繼承AbstractInterceptor
,而後複寫方法就能夠啦!至於爲何繼承AbstractInterceptor
而不是直接實現Interceptor
接口,是由於AbstractInterceptor
已經幫我們實現了空的init()
和destroy()
方法,不須要我們本身去複寫了,我們直接複寫intercept()
方法就能夠啦!如今,我們大體瞭解一下這 3 個方法的做用:
init()
方法,通常用來進行初始化操做;destroy()
方法,通常用來進行釋放資源的操做;intercept()
方法,該方法是實現攔截功能的主要方法,我們就在該方法中編寫攔截的邏輯。接下來,我們在看看以上接口和抽象類的具體代碼:
Interceptor
接口:
package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import java.io.Serializable; public interface Interceptor extends Serializable { /** * Called to let an interceptor clean up any resources it has allocated. */ void destroy(); /** * Called after an interceptor is created, but before any requests are processed using * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving * the Interceptor a chance to initialize any needed resources. */ void init(); /** * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code. * * @param invocation the action invocation * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself. * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}. */ String intercept(ActionInvocation invocation) throws Exception; }
AbstractInterceptor
接口:
package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; /** * Provides default implementations of optional lifecycle methods */ public abstract class AbstractInterceptor implements Interceptor { /** * Does nothing */ public void init() { } /** * Does nothing */ public void destroy() { } /** * Override to handle interception */ public abstract String intercept(ActionInvocation invocation) throws Exception; }
如上面的代碼所示,展現了Interceptor
接口和AbstractInterceptor
抽象類的源碼。下面,我們以繼承AbstractInterceptor
抽象類爲例進行演示:
package com.hit.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; import org.apache.struts2.ServletActionContext; /** * @author 維C果糖 * @create 2017-03-31 */ public class WrongCodeInterceptor extends AbstractInterceptor { /** * 經過攔截功能,驗證用戶是否登陸 */ public String intercept(ActionInvocation invocation) throws Exception { UserInfo info = (UserInfo) ServletActionContext.getRequest().getSession().getAttribute("user"); if(info != null && !info.getName().equals("") && !info.getPwd().equals("")) { return invocation.invoke(); } return "login"; } }
UserInfo
類文件:
/** * @author 維C果糖 * @create 2017-03-31 */ public class UserInfo { String name; String pwd; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } }
在前面,我們用了很大篇幅的內容講述了攔截器如何實現,所以,我相信你們實現攔截器已經沒有問題啦!接下來,我們在看看,如何在 XML 文件中配置攔截器,使我們的攔截器生效。
在配置攔截器以前,有 4 個名稱的概念須要你們先了解一下,分別爲:Join Point
、Pointcut
、Advice
和Advisor
.
接下來,給出 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.hit.interceptor.WrongCodeInterceptor"/> <mvc:interceptor> <mvc:mapping path="/demo/hello.do"/> <!-- 定義在 mvc:interceptor 下面的 Interceptor,表示對特定的請求進行攔截 --> <bean class="com.hit.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
標籤主要有兩種方式:
mvc:interceptor
標籤進行聲明,使用這種方式進行聲明的 Interceptor 能夠經過mvc:mapping
子標籤來定義須要進行攔截的請求路徑。此外,因爲攔截器是 AOP 編程思想的典型應用,也就意味着我們能夠「切」到具體的「面」進行某些操做。例如,
<bean id="WrongCodeInterceptor" class="com.hit.interceptor.WrongCodeInterceptor"> <property name="userName" value="user-module"></property> </bean> <bean id="loginInterceptor" class="com.hit.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.hit.*.demo..*.*(..)) " /> <aop:advisor advice-ref="loginInterceptor" pointcut="execution(* com.hit.*.demo..*.*(..))" /> </aop:config>
如上所示,我們實現了切入到「面」進行特定的攔截功能,其中pointcut
表示「切入點」,advisor
表示要注入到pointcut
的代碼。你們可能會對pointcut
中的*
符合有所疑惑,它是「通配符」,表示能夠匹配該位置上的任何名稱。固然,若是我們要想使用aop
標籤,就得先在配置文件中就得進行聲明啦!此外,若是你們想進一步瞭解切入點pointcut
的表達式的話,能夠參考博文「 Spring 框架中切入點 pointcut 表達式的經常使用寫法 」。