Spring Interceptor攔截器
1. 攔截器
1.1 概念
Java 裡的攔截器是動態攔截 action 呼叫的物件。它提供了一種機制可以使開發者可以定義在一個 action 執行的前後執行的程式碼,也可以在一個 action 執行前阻止其執行,同時也提供了一種可以提取 action 中可重用部分的方式。在AOP(Aspect-Oriented Programming,面向切面程式設計)中攔截器用於在某個方法或欄位被訪問之前進行攔截,然後在之前或之後加入某些操作。
1.2 原理
攔截器 Interceptor 的攔截功能是基於 Java 的動態代理來實現的,具體可以參考博文“
2 實現方法
在 Spring 框架之中,咱們要想實現攔截器的功能,主要通過兩種途徑,第一種是實現HandlerInterceptor
介面,第二種是實現WebRequestInterceptor
介面。接下來,咱們分別詳細的介紹兩者的實現方法。
2.1 HandlerInterceptor 介面
在HandlerInterceptor
介面中,定義了 3 個方法,分別為preHandle()
、postHandle()
和afterCompletion()
HandlerInterceptor
介面來實現攔截器的功能。不過在 Spring 框架之中,其還提供了另外一個介面和一個抽象類,實現了對HandlerInterceptor
介面的功能擴充套件,分別為:AsyncHandlerInterceptor
和HandlerInterceptorAdapter
.
對於AsyncHandlerInterceptor
介面,其在繼承HandlerInterceptor
介面的同時,又聲明瞭一個新的方法afterConcurrentHandlingStarted()
;而HandlerInterceptorAdapter
AsyncHandlerInterceptor
介面的同時,又複寫了preHandle
方法。因此,AsyncHandlerInterceptor
更像是一個過渡的介面。
在實際應用中,咱們一般都是通過實現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 渲染了對應的檢視之後執行,這個方法的主要作用是用於進行資源清理的工作。
接下來,咱們在看看以上介面和抽象類的具體程式碼:
HandlerInterceptor
介面:
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
介面:
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......");
}
}
2.2 WebRequestInterceptor 介面
在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 中對它定義了三個常量,分別為:
- 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.
接下來,咱們在看看以上介面和抽象類的具體程式碼:
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......");
}
}
2.3 AbstractInterceptor 抽象類
除了上面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;
}
}
3 配置攔截器
在前面,咱們用了很大篇幅的內容講述了攔截器如何實現,因此,我相信大家實現攔截器已經沒有問題啦!接下來,咱們在看看,如何在 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.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
標籤主要有兩種方式:
- 直接定義一個 Interceptor 實現類的 bean 物件,使用這種方式宣告的 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 表示式的常用寫法 ”。
參考文獻:
[1] struts2使用AbstractInterceptor實現攔截器
[2] Spring AOP中Pointcut,dvice 和 Advisor三個概念介紹
[3] Java三大器之攔截器(Interceptor)的實現原理及程式碼示例
[4] SpringMVC中使用Interceptor攔截器
[5] Java過濾器與SpringMVC攔截器之間的關係與區別