2009年3月19日 星期四

[Struts2] 內建的 Interceptors

在 Struts2 framework 中,擁有許多的內建 Interceptors,這讓 programmers 可以很方便的 reuse,甚至我們根本就不需要開發自己的 interceptors,因為內建的 interceptors 已經做了大部分的工作!這種好處不僅僅在 interceptor 中才有,其他的 Struts2 components 也有這樣的特性:framework 已經內建好很多很好用的 components,讓我們很專心的在開發我們的 business logic。

要知道 Struts2 中內建有哪些的 interceptors,我們可以從 struts-default.xml 中得到資訊!以下我只會簡單的介紹幾個比較重要的 interceptors,剩下的就留給有興趣的你來 survey。

1. Utility Interceptors

首先,我們先看看一些工具類的 interceptors,這類的 interceptors 主要是幫助 programmer 來開發、調校與找 bug 所使用的。

這個 interceptor 很直觀就是用來測時間的!這個 interceptor 可以幫助 programmer 測量每一次 request 執行所花費的時間,而且很有趣的是,如果我們將這個 interceptor 放在 stack 的最上層,我們測量的時間就是整個 request 執行的時間;如果我們將 timer interceptor 放在 stack 的最下層,那我們量測的時間就會是 Action 執行的時間!

所以我們可以藉由調整這個 interceptor 的位置來測量 request 的時間或是 Action 執行時間。

這個 interceptor 提供一個很簡單的 logging 機制,此 interceptor 會在 preprocessing 與 postprocessing 進行 logging 的動作。這樣可以很容易的 debug,因為我可以追蹤使用者的使用紀錄,進而找出問題的所在。

2. Data Transfer Interceptors

資料轉換是一個從 client 到 server 端很重要的角色,因為使用者填寫完畫面上的表單後,接著就會透過 interceptors 將資料 mapping 到 server 端的 java 物件。也就是將 request parameter 中的資料移動到 Action 之中。

這是一個很重要的 interceptor,這個 interceptor 會主要將 request parameters 轉換到 ValueStack 中相對映的 properties。這個 interceptor 也會與 OGNL 一起合作將 parameter 作轉換,並且可以幫助我們將 parameter 的值轉換到 ModelDriven 的 Action 中。不過,param interceptor 並不會知道資料確實是要存放在哪,他只會將 parameter 的值轉換到第一個相符的 property 中。這點一開始會令人感到很詭異,不過因為現在還不會對 ValueStack 作很深入的探討,所以你就先記著這個特性,之後我們在深入的討論 ValueStack。

這個 interceptor 跟上面的 param interceptor 功能很像,都是將 parameter 中的值轉換到 ValueStack 中,不過不一樣的是,這個是將 configuration file 中設定的 parameter 轉換到 ValueStack 中:

<action name="myAction" class="silver8250.my.MyAction">
<param name="firstName">Silver</param>
<param name="lastName">Su</param>
</action>
從上面的 configuration 中可以得知,我們可以在 configuration 中先設定預設 parameter 的值,static-param interceptor 會幫我們將這些設定在 configuration 中的 parameter 轉換到 Action 中。有一點值得注意的是,因為 static-param interceptor 在 stack 中是排在 param interceptor 之前,那就代表著如果使用者的表單中也有 firstName 與 lastName 的話,當表單送交後,就會覆蓋原先在 configuration 中設定的值。這樣可以讓使用者沒有填的欄位具有 default value。

這個 interceptor 也是相當重要的一個,因為他提供了 Dependency Injection(DI) 的功能。DI 簡單來說就是我們不要直接的去要求某些物件,反而是讓 framework 自己給我某些物件!如果你有熟悉 Spring framework 的話,DI 對你來說應該就是家常便飯了!

如果你有寫過 Struts 1.x,我想以下的例子你就可以更深刻的體會了!在 Struts 1.x 中,execute() method 都會與 HttpServletRequest 等物件有相依性,這樣就造成了測試上的不方便,因為我們要將這些 Container 物件傳遞給 execute() method,我們才能進行測試的工作!也就是我們要使用 Mock Object 來測試,這樣讓測試的工作不單純。不過請你回想一下在 Strut2 中我們的 Action execute() method 是很 pure 的,我們無須讓呼叫者傳遞任何的 parameters 給我們!但是你應該會跟我一樣很 confuse!我們要怎要拿到 HttpServletRequest 物件呢?別急!接下來就是 Servlet-config interceptor 的能力要發揮了~

Servelt-config interceptor 提供了以下的 interfaces 讓 programmer 可以 implement:

  1. ServletContextAware - 設定 ServletContext 物件給 Action
  2. ServletRequestAware - 設定 HttpServletRequest 物件給 Action
  3. ServletResponseAware - 設定 HttpServletResponse 物件給 Action
  4. ParameterAware - 設定 request parameter 的 Map 物件給 Action
  5. RequestAware - 設定 request 的 Map 物件給 Action
  6. SessionAware - 設定 session 的 Map 物件給 Action
  7. ApplicationAware - 設定 application 的 Map 物件給 Action
  8. PrincipalAware - 設定 Principal 物件給 Action,用於 security

在上面所列出的 8 項中,最常用的應該就是前 7 個,而且這每一個 interface 都只有一個 setter method(如:SessionAware 的 setSession(Map<String, Object> session) method)。第 1~3 的 interfaces 屬於將原始的 JSP 物件設定給 Action,而 4~7 是 Struts2 framework 將各個在 JSP 的隱含物件(Implicit Object)提供 Map 方式存取!

只要我們的 Action implement SessionAware 並且實做出 setSession() method,我們就可以在 Action 中存取 Session Map 物件,如下:




public MyAction implements SessionAware
{
private Map<String, Object> session;
public setSession(Map<String, Object> session)
{
this.session = session;
}
public String execute()
{
//對 session 物件進行存取!
}
}

上面的程式中我們先宣告一個 session 的 Map 物件,這是為了方便我們在 execute() method 中存取 session,所以我們透過 setSession() method 將 framework 給我們的 session Map 物件設定到我們的 session 變數。其他的 interface 用法也如同 SessionAware 的使用方式。

這個 interceptor 幫我們將 client 端 form 的 multipart request 轉換成 Action 中的物件,不過這轉換的機制跟 param interceptor 有點不一樣,因為在 defaultStack 中,Fileupload interceptor 是被安排在 param interceptor 之前,所以,Fileupload 是將 parameter 的值轉換成像是一般的 parameter 一樣(有興趣的你可以自行研究一下 API 的內容~),然後在交給 param interceptor 去處理,而這樣的處理就變得更單純了!

3. Workflow Interceptors

Workflow 類型的 interceptors 提供了許多在執行 Action 之前的最後作業,所以 workflow 類型的 interceptors 通常是在 stack 中的最後面幾個,因為當所有的 data 都完成轉換後,workflow 就會開始進行一連串的流程作業。

而這裡所謂的 workflow 是指:一系列的流程,從 interceptor 開始到 Action 執行,產生 Result 最後再回到 interceptor 的過程。而 workflow interceptors 扮演著檢驗流程中處理的狀態,如果流程中有任何狀態是有問題的,就會進行非正常的流程處理。

  • Workflow interceptor(default stack)
    別懷疑,workflow 確實是一個 interceptor!這個 interceptor 會負責 data validation,並且會將驗證錯誤的資訊傳送到 client 端的頁面。這裡所謂的 data validation 就是我們之前在深入實做 Action 中提到的 basic validation,也就是我們在 Action 中撰寫的 validate() method。下面的程式碼就是簡化過得 workflow interceptor:(雖然簡化,不過功能很齊全!)


public String intercept(ActionInvocation invocation) throw Exception
{
Action action = invocation.getAction();
if (action instanceof Validateable)
{
Validateable validateable = (Validateable) action;
validateable.validate();
}
if (action instanceof ValidationAware)
{
ValidationAware validationAwareAction =
(ValidationAware) action;
if (validationAwareAction.hasErrors())
{
return Action.INPUT;
}
}
return invocation.invoke();
}

如果我們的 Action 是 extends ActionSupport 的話,ActionSupport 就會幫我們 implement Validateable 與 ValidationAware interfaces。第一個 if statement 用來判斷此 Action 是否為一個 Validateable 物件,並且執行 validate() method。第二個 if statement 則是在判斷是否有 Error message 被儲存在 ValidationAware 中。

另一個有趣的功能就是 workflow 可以透過 param interceptor 被調整,怎麼說呢!在 struts-default.xml 中我們可以看到 workflow interceptor 已經被調整成如下:


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

上面的設定中我們告知 workflow interceptor:當執行 validate 時,請忽略 input, back, cancel, browse 為 Action 進入點的 methods。關於目前我們只有使用最簡單的 Action,也就是單一的 execute() method 為進入點的 Action。所以目前你可能會沒有感覺,但是我想表達的是關於 workflow interceptor 是可以被這樣用來調整成適合我們的!以下就列出一些 workflow 准許我們調整的 parameters。

參數 預設值 說明
alwaysInvokeValidate true / false true

設定是否總是呼叫 validate() method

inputResultName String Action.INPUT 設定如果驗證失敗則要產生的頁面
excludeMethods String N/A 設定不執行驗證的 Action 進入點 method

上面的 workflow interceptor 已經提供了驗證的功能了!那 Validation interceptor 要用來做什麼呢?你應該會很困惑~雖然 workflow interceptor 已經做了最基本的驗證功能,不過對於一個大型的 web application 來說,要將類似的驗證邏輯都重複寫在不同的 Action validate() method 中其實很不方便!所以 Validation interceptor 就是用來解決這樣的問題,在 Struts2 中提供了另一種 Validation framework,讓 programmer 可以將驗證邏輯撰寫一次,然後藉由設定檔在不同的 Action 進行相同的驗證!這個機制的好處對於大型的 web application 來說是比較好得!畢竟複製一堆相同程式碼在不同的地方,對於日後的維護是相當累的!

我們在這裡並不會詳談關於 Validation framework 的部份,不過可以簡單的介紹一下。當 programmer 將各個 Action 的驗證規則定義好後,validation interceptor 就會根據設定檔進行驗證的工作,當有發現驗證失敗的地方,會將錯誤訊息儲存在 ValidationAware 中,所以在 workflow interceptor 中還是需要將這些錯誤訊息顯示在 client 端頁面上!不過我們還是可以撰寫 validation() method,這個 method 的內容還是會被執行的!

另外,在 interceptor stack 中,validation interceptor 一定要在 workflow 之前,否則驗證有錯誤的訊息就不會被顯示了!

Prepare interceptor 提供了一種方式讓我們可以介入整個 request 流程,也就是如果我們的 Action implements Preparable interface 就可以擁有這樣的好處(P.S. ActionSupport 沒有 implement Preparable),並且實做 prepare() method 就可以!

當 Prepare interceptor 執行時會先檢查該 Action 是否為一個 Preparable,如果是,就會呼叫 prepare() method。所以我們可以在 Action 被執行之前(更嚴格的說是在 Prepare interceptor 之後的動作執行前),將前置動作寫在 prepare() method 中。

Prepare interceptor 也提供一個 parameter 可以調校:

alwaysInvokePrepare - 是否總是呼叫 prepare() method,預設是 true。

這個 interceptor 對我們來說並不陌生,很直觀的,他就是用來處理 ModelDriven 的 Action。ModelDriven interceptor 執行時,會先判斷此 Action 是否有 implements ModelDriven interface,接著就會將 getModel() method 中的 model 儲存到 ValueStack 中。

需要注意的是,ModelDriven interceptor 一定要安排在 static-parameter 與 parameter interceptor 之前,否則將不會發揮功能!

4. Miscellaneous Interceptors

這類的 interceptors 是屬於一些輔助型的,而且這些 interceptors 對整個 stack 的其他 interceptors 進行點綴。

這個 interceptor 很重要,因為他會負責處理整個 request 流程中的 exception。在 defaultStack 中 exception interceptor 是第一個被呼叫的 interceptor,並且會擷取使用者定義的 exception,將訊息顯示在使用者定義的 exception page。也因為最上層的 interceptor 所以他會擷取所有的 exception,若是放在最下層,則只會擷取到 Action 所產生的 exception。

<global-results>
<result name="error">/WEB-INF/pages/error.jsp</result>
</global-results>
<global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error" />
</global-exception-mappings>

上面的設定是屬於 configuration file中,global-results tag 表示當某個 Action 所回傳的控制字串不再 Action 的定義內,framework 會去搜尋 global-results。也就是說 global-results 是在這個 configuration file 中可以共用的!另外,global-exception-mapping tag 就是定義 exception interceptor 要幫我們處理哪些 exception,並且要將錯誤的訊息顯示在哪個頁面!

這個 interceptor 是可以讓我們將 ModelDriven 所使用的 model object 儲存在 session 等等的 scope,不過目前我不會詳細的介紹!

在這裡我們介紹了很多有用的 interceptors,有興趣的你可以各自深入的瞭解,在這裡我只能很簡略的討論各個 interceptors 的功能而已!

沒有留言: