1. 程式人生 > 其它 >Struts 2知識回顧----攔截器(Intercept)總結

Struts 2知識回顧----攔截器(Intercept)總結

什麼是Struts2攔截器?

從軟體構架上來說,攔截器是實現了面向方面程式設計的元件。它將影響了多個業務物件的公共行為封裝到一個個可重用的模組,減少了系統的重複程式碼,實現功能的高度內聚,確保了業務物件的整潔和純度。

從Java程式碼上來說,它就是一個普度的Java物件,它只需要實現一個名為Interceptor的介面。

為什麼要使用攔截器?

攔截器消除了動作元件中的橫切任務(cross-cutting task)。例如,日誌記錄是一個典型的橫切關注。以前,我們可能需要為每個動作元件準備一些記錄日誌的程式碼。雖然這樣看上去也沒什麼錯誤,但是一個不可 以忽視的事實是——這些用於記錄日誌的程式碼並不是動作元件與模型互動的一部分。並且,日誌記錄是我們想為系統處理的每一個請求完成的管理性任務,它不是一 個動作元件所特有的,而是貫穿了所有動作元件。Struts 2將這個功能放在了更高的層次上,我們可以能把日誌記錄的程式碼轉移到動作元件之外,從而讓動作元件更加簡潔清晰。

攔截器元件提供了一個不錯的場所,來將不同的橫切關注點邏輯分成有層次的、可重用的部件。

攔截器的工作原理?

當框架接收到一個請求的時候,它首先必須決定這個URL對映到哪一個動作元件。這個動作元件的一個例項就會被加入到一個新建立的 ActionInvocation例項中。接著框架諮詢宣告性架構(XML配置檔案或Java註解),以發現哪一些攔截器應該被觸發,以及按照什麼樣的順 序觸發。將這些攔截器的引用新增到ActionInvocation中。除了這些核心元素,ActionInvocation也擁有其他重要的資訊 (Servlet請求物件和當前動作元件可用的結果元件列表)。

當ActionInvocation被建立完畢,並且填充了需要的所有物件和資訊,就可以被使用了。ActionInvocation公開了一個 invoke()方法,框架通過呼叫這個方法開始動作的執行。當框架呼叫了這個方法時,ActionInvocation通過執行攔截器棧中的第一個攔截 器開始這個呼叫過程。需要注意的是,invoke()並不是總是指向攔截器棧的第一個攔截器,ActionInvocation負責跟蹤執行過程達到的狀 態,並且把控制交給棧中合適的攔截器(通過呼叫攔截器的intercept()方法將控制權交給攔截器)。通過遞迴呼叫(遞 歸過程?框架通過第一次呼叫ActionInvocation物件的invoke()方法開始了這個過程。ActionInvocation通過呼叫攔截 器的intercept()方法把控制權交給第一個攔截器。重要的是,intercept()方法把ActionInvocation例項作為一個引數。 在攔截器的處理過程中,他會呼叫ActionInvocation例項引數的invoke()方法來繼續呼叫後續攔截器的遞迴過程。因此,在通常的執行 中,呼叫過程向下通過所有攔截器,直到棧中再也沒有攔截器時,觸發動作。另外,ActionInvocation在內部管理處理狀態,因此它總是直到自己 現在處在棧的什麼位置。)ActionInvocation的invoke()方法,攔截器棧中後續的攔截器繼續執行,最終執行動作。這是 因為每一次invoke()方法被呼叫時,ActionInvocation都會查詢自身的狀態,呼叫接下來的攔截器。在所有攔截器都被呼叫之 後,invoke()方法會促使動作類執行。

下面看一個簡單的例子,演示上面所說的理論:

TestAction

public class TestAction extends ActionSupport
{
private static final long serialVersionUID = 2753590609366162370L;

@Override
public String execute() throws Exception
{
System.out.println("execute");

return SUCCESS;
}

}

Interceptor1

public class Intercept1 implements Interceptor
{
private static final long serialVersionUID = 8596224826058233434L;

public void destroy()
{

}

public void init()
{

}

public String intercept(ActionInvocation invocation) throws Exception
{
System.out.println("interceptor1 begin");

String result = invocation.invoke();

System.out.println("interceptor1 end");

return result;
}

}

Interceptor2

public class Intercept2 implements Interceptor
{
private static final long serialVersionUID = -1580591331691823185L;

public void destroy()
{

}

public void init()
{

}

public String intercept(ActionInvocation invocation) throws Exception
{
System.out.println("interceptor2 begin");

String result = invocation.invoke();

System.out.println("interceptor2 end");

return result;
}

}

Interceptor3

public class Intercept3 implements Interceptor
{
private static final long serialVersionUID = 7081124564804422023L;

public void destroy()
{

}

public void init()
{

}

public String intercept(ActionInvocation invocation) throws Exception
{
System.out.println("interceptor3 begin");

String result = invocation.invoke();

System.out.println("interceptor3 end");

return result;
}

}

有三個自定以的攔截器,他們只是簡單的輸出幾個字串,攔截器開始將執行輸出 "interceptor'x' begin";攔截器結束時輸出" interceptor'x' end";

將他們設定在一個指定的動作上

 1 <interceptors>
2 <interceptor name="interceptor1" class="com.test.suxiaolei.intercept.Intercept1"></interceptor>
3 <interceptor name="interceptor2" class="com.test.suxiaolei.intercept.Intercept2"></interceptor>
4 <interceptor name="interceptor3" class="com.test.suxiaolei.intercept.Intercept3"></interceptor>
5
6 <interceptor-stack name="myStack">
7 <interceptor-ref name="interceptor1"></interceptor-ref>
8 <interceptor-ref name="interceptor2"></interceptor-ref>
9 <interceptor-ref name="interceptor3"></interceptor-ref>
10 <interceptor-ref name="defaultStack"></interceptor-ref>
11 </interceptor-stack>
12 </interceptors>
13
14 <action name="test" class="com.test.suxiaolei.action.TestAction">
15 <result name="success">/index.jsp</result>
16 <interceptor-ref name="myStack"></interceptor-ref>
17 </action>

提交表單,控制檯的輸出結果:

interceptor1 begin
interceptor2 begin
interceptor3 begin
execute
interceptor3 end
interceptor2 end
interceptor1 end

從控制檯輸出的結果可以看出,在提交資料到框架時,框架呼叫攔截器的過程,首先框架會根據URL請求建立指定的動作TestAction,將 TestAction的例項和TestAction相關的攔截器引用myStack放入一個新的ActionInvocation物件中(還包含其他信 息),然後框架呼叫ActionInvocation的invoke()方法,此時開始了攔截器棧呼叫過程,最開始呼叫攔截器棧的第一個攔截器也就是 Intercept1,攔截器執行完預處理後,因為intercept()方法接收一個ActionInvocation物件作為引數,在 Intercept1.intercept()方法中繼續呼叫 ActionInvocation物件的invoke()方法將向下繼續呼叫棧中餘下的攔截器Intercept2...一直到棧中沒有攔截器為止,最後 執行動作元件。在結果被呈現之後,攔截器會按照相反的順序再觸發一遍,使他們可以進行後處理。

攔截器工作原理圖:

攔截器觸發時能夠做些什麼?

1. 做一些預處理。在這個階段攔截器可以用來準備、過濾、改變或者操作任何可以訪問的重要資料。這些資料包括所有與當前請求相關的關鍵物件和資料,也包括動作。

2. 通過呼叫invoke()方法將控制轉移給後續的攔截器,直到動作。或者通過返回一個控制字串中斷執行。在這個階段,如果攔截器決定請求不應該繼續,他 可以不呼叫ActionInvocation例項上的invoke()方法,而是直接返回一個控制字串。通過這種方式可以停止後續的執行,並且決定哪個 結果被呈現。

3. 做一些後加工。在這個階段,任何一個返回的攔截器可以修改可以訪問的物件的資料作為後加工,但是此時結果已經確定了。

怎麼宣告攔截器?

下面為Struts 2為我們提供的struts-default.xml檔案部分:

<struts>
...
<interceptors>
<interceptor name="alias" class="com.opensymphony.xwork2.interceptor.AliasInterceptor"/>
<interceptor name="autowiring" class="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"/>
<interceptor name="chain" class="com.opensymphony.xwork2.interceptor.ChainingInterceptor"/>
<interceptor name="conversionError" class="org.apache.struts2.interceptor.StrutsConversionErrorInterceptor"/>
<interceptor name="cookie" class="org.apache.struts2.interceptor.CookieInterceptor"/>
<interceptor name="clearSession" class="org.apache.struts2.interceptor.ClearSessionInterceptor"/>
<interceptor name="createSession" class="org.apache.struts2.interceptor.CreateSessionInterceptor"/>
<interceptor name="debugging" class="org.apache.struts2.interceptor.debugging.DebuggingInterceptor"/>
<interceptor name="execAndWait" class="org.apache.struts2.interceptor.ExecuteAndWaitInterceptor"/>
<interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>
<interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>
<interceptor name="i18n" class="com.opensymphony.xwork2.interceptor.I18nInterceptor"/>
<interceptor name="logger" class="com.opensymphony.xwork2.interceptor.LoggingInterceptor"/>
<interceptor name="modelDriven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/>
<interceptor name="scopedModelDriven" class="com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor"/>
<interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>
<interceptor name="actionMappingParams" class="org.apache.struts2.interceptor.ActionMappingParametersInteceptor"/>
<interceptor name="prepare" class="com.opensymphony.xwork2.interceptor.PrepareInterceptor"/>
<interceptor name="staticParams" class="com.opensymphony.xwork2.interceptor.StaticParametersInterceptor"/>
<interceptor name="scope" class="org.apache.struts2.interceptor.ScopeInterceptor"/>
<interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"/>
<interceptor name="timer" class="com.opensymphony.xwork2.interceptor.TimerInterceptor"/>
<interceptor name="token" class="org.apache.struts2.interceptor.TokenInterceptor"/>
<interceptor name="tokenSession" class="org.apache.struts2.interceptor.TokenSessionStoreInterceptor"/>
<interceptor name="validation" class="org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor"/>
<interceptor name="workflow" class="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"/>
<interceptor name="store" class="org.apache.struts2.interceptor.MessageStoreInterceptor"/>
<interceptor name="checkbox" class="org.apache.struts2.interceptor.CheckboxInterceptor"/> <interceptor name="profiling" class="org.apache.struts2.interceptor.ProfilingActivationInterceptor"/> <interceptor name="roles" class="org.apache.struts2.interceptor.RolesInterceptor"/> <interceptor name="jsonValidation" class="org.apache.struts2.interceptor.validation.JSONValidationInterceptor"/> <interceptor name="annotationWorkflow" class="com.opensymphony.xwork2.interceptor.annotations.AnnotationWorkflowInterceptor"/> <interceptor name="multiselect" class="org.apache.struts2.interceptor.MultiselectInterceptor"/> <!-- Basic stack --> <interceptor-stack name="basicStack"> <interceptor-ref name="exception"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="prepare"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> </interceptor-stack> <!-- Sample validation and workflow stack --> <interceptor-stack name="validationWorkflowStack"> <interceptor-ref name="basicStack"/> <interceptor-ref name="validation"/> <interceptor-ref name="workflow"/> </interceptor-stack> <!-- Sample JSON validation stack --> <interceptor-stack name="jsonValidationWorkflowStack"> <interceptor-ref name="basicStack"/> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel</param> </interceptor-ref> <interceptor-ref name="jsonValidation"/> <interceptor-ref name="workflow"/> </interceptor-stack> <!-- Sample file upload stack --> <interceptor-stack name="fileUploadStack"> <interceptor-ref name="fileUpload"/> <interceptor-ref name="basicStack"/> </interceptor-stack> <!-- Sample model-driven stack --> <interceptor-stack name="modelDrivenStack"> <interceptor-ref name="modelDriven"/> <interceptor-ref name="basicStack"/> </interceptor-stack> <!-- Sample action chaining stack --> <interceptor-stack name="chainStack"> <interceptor-ref name="chain"/> <interceptor-ref name="basicStack"/> </interceptor-stack> <!-- Sample i18n stack --> <interceptor-stack name="i18nStack"> <interceptor-ref name="i18n"/> <interceptor-ref name="basicStack"/> </interceptor-stack> ... </interceptors> <default-interceptor-ref name="defaultStack"/> <default-class-ref class="com.opensymphony.xwork2.ActionSupport"/> </package></struts>

在檢視它的關於interceptor部分的dtd檔案:

<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-exception-mappings?, action*)>

...

<!ELEMENT interceptors (interceptor|interceptor-stack)+>

<!ELEMENT interceptor (param*)>
<!ATTLIST interceptor
name CDATA #REQUIRED
class CDATA #REQUIRED
>

<!ELEMENT interceptor-stack (interceptor-ref*)>
<!ATTLIST interceptor-stack
name CDATA #REQUIRED
>

<!ELEMENT interceptor-ref (param*)>
<!ATTLIST interceptor-ref
name CDATA #REQUIRED
>

...

檢視dtd文件可以看到interceptors標籤要定義在package標籤下,我們對照struts-default.xml可以看到interceptors標籤確實定義在package標籤下,並且在dtd文件中package元素右邊說明中interceptors標籤右邊帶有一個"?"符號,表示該標籤要麼有且只有1個,要麼沒有。

再接著檢視dtd文件interceptors元素右面有"(interceptor|interceptor-stack)*"的一串字元式子,表示 interceptors標籤下可以有0個或多個interceptor標籤和interceptor-stack標籤,並且不分順序。此時再對比struts-default.xml檔案可以看到interceptors標籤下確實有許多interceptor標籤和interceptor-stack標籤。interceptor標籤是用於宣告一個獨立的攔截器的,而interceptor-stack標籤用於組織一系列攔截器的 。

在dtd文件中可以看到interceptor-stack的 name屬性為#REQUIRED表示這是必須的屬性,你宣告一個攔截器棧必須給棧取一個名字。而interceptor標籤中有兩個屬性name和 class它們都是#REQUIRED,所以你想定義一個攔截器就必須指明這兩個屬性的值,name為攔截器的名字,class為這個攔截器會使用哪一個 類作為它的處理器。還有interceptor- stack標籤可以包含許多interceptor-ref標籤,這些標籤是用於引用你使用interceptor標籤宣告的攔截器或另外的攔截器棧,它 的name屬性的值為interceptor標籤宣告的攔截器的name值或其他攔截器棧的name值,該屬性也是#REQUIRED。可以再次對比 struts-default.xml文件,可以看到這個兩個標籤的用法與我們的理解完全一致。若還有什麼不清楚可以去下載struts的dtd文件那裡 有具體的說明。

最後一個package可以定義一組預設攔截器(有且只能有1個,dtd規定) ,例如在struts-default.xml文件中

<default-interceptor-ref name="defaultStack"/>

定義了預設的攔截器組,這個預設的攔截器組會與這個包內沒有顯示宣告自己的攔截器的所有動作相關聯。

現在知道了這些,我們就可以定義攔截器了。

攔截器和攔截器棧的宣告:

<interceptors>
<interceptor name="interceptor1" class="com.test.suxiaolei.intercept.Intercept1"></interceptor>
<interceptor name="interceptor2" class="com.test.suxiaolei.intercept.Intercept2"></interceptor>
<interceptor name="interceptor3" class="com.test.suxiaolei.intercept.Intercept3"></interceptor>

<interceptor-stack name="myStack">
<interceptor-ref name="interceptor1"></interceptor-ref>
<interceptor-ref name="interceptor2"></interceptor-ref>
<interceptor-ref name="interceptor3"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>

最後,一部分攔截器是可以接收引數,若一個攔截器接收引數,那麼interceptor-ref元素是向它們傳入引數的地方。比如,struts-default.xml文件中的workflow攔截器

<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>


怎麼構建自定義攔截器?

通過實現Interceptor介面

  在編寫一個攔截器時需要實現com.opensymphony.xwork2.interceptor.Interceptor介面

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;

}

可以看到,這個簡單的介面只定義了3個方法。前兩個方法時典型的生命週期方法,讓你初始化和清理資源。真正的業務邏輯發生在intercept()方法中。這個方法被ActionInvocation.invoke()方法遞迴呼叫。

下面演示一個簡單的許可權驗證的例子:

  它的工作原理很簡單,當一個請求訪問一個安全動作時,我們想檢查請求是否是一個通過身份驗證的使用者發出的。

許可權認證攔截器:

public class AuthenticationInterceptor implements Interceptor
{
private static final long serialVersionUID = -1500368808387165682L;

public void destroy()
{

}

public void init()
{

}

public String intercept(ActionInvocation invocation) throws Exception
{
Map<String , Object> session = invocation.getInvocationContext().getSession();

User user = (User) session.get("USER");

if (user == null)
{
return Action.INPUT;
}
else
{
Action action = (Action) invocation.getAction();
if (action instanceof UserAware)
{
((UserAware) action).setUser(user);
}
}

return invocation.invoke();
}
}

登陸動作元件:

public class LoginAction extends ActionSupport implements SessionAware
{
private static final long serialVersionUID = 3767975035031881006L;

private String username;
private String password;
private Map<String , Object> session;

public String getUsername()
{
return username;
}

public void setUsername(String username)
{
this.username = username;
}

public String getPassword()
{
return password;
}

public void setPassword(String password)
{
this.password = password;
}

public void setSession(Map<String , Object> session)
{
this.session = session;
}

@Override
public String execute()
{
User user = null;

if (getUsername().equals(getPassword()))
{
user = new User();
user.setUsername(username);
user.setPassword(password);
}

if (user == null)
{
return INPUT;
}
else
{
session.put("USER" , user);
}

return SUCCESS;
}

}

安全動作元件,需要登陸後才可以進入:

public class TestAction extends ActionSupport implements UserAware
{
private static final long serialVersionUID = 2753590609366162370L;

private User user;

public User getUser()
{
return user;
}

@Override
public String execute() throws Exception
{
return SUCCESS;
}

public void setUser(User user)
{
this.user = user;
}

}

宣告攔截器和為Action配置攔截器:

<interceptors>
<interceptor name="authentication" class="com.test.suxiaolei.intercept.AuthenticationInterceptor"></interceptor>

<interceptor-stack name="myStack">
<interceptor-ref name="authentication"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>

<action name="login" class="com.test.suxiaolei.action.LoginAction">
<result name="success">/test.jsp</result>
<result name="input">/input.jsp</result>
</action>

<action name="test" class="com.test.suxiaolei.action.TestAction">
<result name="success">/index.jsp</result>
<result name="input">/input.jsp</result>
<interceptor-ref name="myStack"></interceptor-ref>
</action>

登入表單:

<body>
<s:form action="login">
<s:textfield name="username" label="username"></s:textfield>
<s:textfield name="password" label="password"></s:textfield>
<s:submit value="submit"></s:submit>
</s:form>
</body>

需要登入許可權才可以提交的表單:

<body>
<s:form action="test">
<s:submit value="submit"></s:submit>
</s:form>
</body>

成功結果頁面:

<body>
This is my JSP page. <br>
</body>

測試:

開啟伺服器tomcat,開啟瀏覽器輸入http://localhost:伺服器埠號/Web專案名稱/test.jsp

點選submit按鈕,得到以下結果

由於沒有登陸,所以許可權不夠,不能夠提交test.jsp的表單,這裡我們設定的攔截器起到了作用,下面我們登入這裡賬號和密碼邏輯很簡單,只要一樣就好了

輸入賬號密碼之後,我們又回到這個頁面了,這次再點選submit按鈕

可以看到我們成功進入了TestAction

建立自定義的攔截器還可以擴充套件com.opensymphony.xwork2.interceptor.AbstractInterceptor類,它 並沒有什麼高階的地方,它僅僅只是幫我們實現了Interceptor介面,幫我們預設實現了init()和destory()方法:

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;
}

  最 後,Struts 2內建了很多的日常Web開發都會用到的攔截器,所以一般不太需要自己開發一個攔截器,內建的攔截器幾乎包含了所有日常需要的功能,要想真正運用好 Struts 2必須要了解他們的工作原理,在Struts 2的官方網站上有詳細的介紹,可以去哪裡找到你需要的東西。