多數(shù)的MVC框架中的Control層,都是一個(gè)Java對(duì)象。按照慣例,我們通常會(huì)把這個(gè)層次上面的Java對(duì)象統(tǒng)稱為Action層。本篇文章,我們就來簡(jiǎn)單介紹一下Struts2中Action的相關(guān)內(nèi)容。
傳統(tǒng)的MVC框架中,Control層一般都是一個(gè)類似與Servlet的一個(gè)Java對(duì)象。因?yàn)閺穆氊?zé)上講,Control層需要完成以下的職責(zé):
1. 接收從Web容器傳遞過來的參數(shù),并做恰當(dāng)?shù)念愋娃D(zhuǎn)化 2. 調(diào)用邏輯處理 3. 搜集數(shù)據(jù),并返回到視圖 而在這個(gè)其中的第一步和第三步,都離不開Web容器中的對(duì)象的處理。
Struts2中的Action,與其他傳統(tǒng)的MVC框架不同,使用了XWork的Action來構(gòu)造Control層。讓我們首先來看看Action的接口定義:
-
-
-
-
-
-
-
-
- public interface Action {
-
-
-
-
-
-
-
-
-
-
- public String execute() throws Exception;
- }
我們只需要實(shí)現(xiàn)這個(gè)接口,就可以在其中編寫業(yè)務(wù)邏輯完成我們的功能。
- public class Index implements Action {
-
- private static final long serialVersionUID = -1070481751569420550L;
-
-
-
-
- public String execute() throws Exception {
-
- return SUCCESS;
- }
- }
在這個(gè)接口定義中,我們可以明顯看到與傳統(tǒng)的MVC框架之間的區(qū)別:
Struts2中的Action,并不需要依賴于特定的Web容器。我們看不到類似HttpServletRequest,HttpServletResponse等Web容器相關(guān)的對(duì)象。 而這一點(diǎn),也帶來了問題:
提問:Struts2的Action并不帶有任何Web容器相關(guān)的對(duì)象,Action又是如何工作在Web容器中的呢? 雖然Struts2的Action只是一個(gè)非常普通的Java對(duì)象,并不具備任何Web容器的特質(zhì),但是我們需要把Action放到一個(gè)更加大的環(huán)境中來看。事實(shí)上,Struts2為Action的執(zhí)行,準(zhǔn)備了完整的數(shù)據(jù)環(huán)境和執(zhí)行環(huán)境。而這個(gè)執(zhí)行環(huán)境,就保證了Action在Web容器中的順利運(yùn)行。
在Struts2中,每個(gè)Http的請(qǐng)求,會(huì)被發(fā)送到一個(gè)Filter。而這個(gè)Filter,就會(huì)針對(duì)每個(gè)請(qǐng)求,創(chuàng)建出一個(gè)代碼的執(zhí)行環(huán)境,并在這個(gè)基礎(chǔ)上,為每個(gè)執(zhí)行環(huán)境配備與之對(duì)應(yīng)的數(shù)據(jù)環(huán)境,這個(gè)數(shù)據(jù)環(huán)境中的內(nèi)容,就來自于Web容器中的一個(gè)又一個(gè)對(duì)象。這樣,就能夠順利調(diào)用Action執(zhí)行代碼而無需擔(dān)心它是否運(yùn)行在Web容器中了。
至于這個(gè)執(zhí)行環(huán)境和數(shù)據(jù)環(huán)境到底是什么,我們接下來會(huì)詳細(xì)講到。
提問:Struts2的Action并不帶有任何Web容器相關(guān)的對(duì)象,Action中又如何與Web容器進(jìn)行通信并獲取Web容器的相關(guān)對(duì)象呢? 剛剛我們提到Struts2會(huì)為每個(gè)Http的請(qǐng)求建立一個(gè)執(zhí)行環(huán)境和數(shù)據(jù)環(huán)境。其中,數(shù)據(jù)環(huán)境就成為了Action獲取Web容器的基礎(chǔ)。所以,當(dāng)Action需要獲取Web容器的相關(guān)對(duì)象,需要通過數(shù)據(jù)環(huán)境來進(jìn)行。
Struts2的Action的這一個(gè)重要特性,至少能為我們帶來以下好處:
1. 使得Struts2的Action非常易于測(cè)試 如果我們完全不考慮Action的執(zhí)行環(huán)境,僅僅把Action看作一個(gè)普通的Java對(duì)象,那么我們甚至可以直接new一個(gè)Action的對(duì)象,通過執(zhí)行其中的方法完成測(cè)試。這樣,我們就不需要任何的Mock,來模擬Web容器的環(huán)境。
2. 結(jié)合Action的執(zhí)行環(huán)境,使得Struts2在Control這個(gè)層次上,能夠定義更加豐富的執(zhí)行層次 因?yàn)锳ction是一個(gè)普通的Java類,而不是一個(gè)Servlet類,完全脫離于Web容器,所以我們就能夠更加方便地對(duì)Control層進(jìn)行合理的層次設(shè)計(jì),從而抽象出許多公共的邏輯,并將這些邏輯脫離出Action對(duì)象本身。事實(shí)上,Struts2也正是這么做的,無論是Interceptor,還是Result,其實(shí)都是抽象出了Action中公共的邏輯部分,將他們放到了Action的外面,從而更加簡(jiǎn)化了Action的開發(fā)。
3. 使得Struts2的Action看上去更像一個(gè)POJO,從而更加易于管理 Struts2的Action是一個(gè)線程安全的對(duì)象。而Web容器傳遞過來的參數(shù),也會(huì)傳遞到Action中的成員變量中。這樣,Action看上去就更像一個(gè)POJO,從而能夠方便的被許多對(duì)象容器進(jìn)行管理。比如說,你可以非常方便得把Action納入到Spring的容器中進(jìn)行管理。
接下來,我們?cè)賮砜纯碨truts2中的Action的生命周期:
這張圖來自于Struts2的Reference,我們能夠在圖中看到許多我們不熟悉的名詞,比如ActionProxy,Interceptor等等。這些都是Struts2的Control層的重要元素,也是Struts2的Control層的一個(gè)層次化的體現(xiàn)。
上面的這張圖基本上能夠概括了Struts2的整個(gè)生命周期。接下來,我們就對(duì)Action中的一些重要元素進(jìn)行簡(jiǎn)單的描述。
在大概了解了Struts2的Action后,我們來重點(diǎn)研究一下在Struts2的Action周圍,為Action進(jìn)行服務(wù)的一些重要元素,這些元素將涵蓋Action的數(shù)據(jù)環(huán)境,Action的執(zhí)行環(huán)境、Action的調(diào)度者、Action的層次結(jié)構(gòu)和Action的執(zhí)行結(jié)果。
ActionContext —— 數(shù)據(jù)環(huán)境 之前我們提到了Struts2的Action并不是一個(gè)Servlet,它是脫離了Web容器的。但是對(duì)于一個(gè)Web框架來說,所有的數(shù)據(jù)請(qǐng)求(Request)和數(shù)據(jù)返回(Response)都來源于Web容器,那么Action在執(zhí)行的時(shí)候,如何去獲取這些數(shù)據(jù)呢?
這個(gè)問題的答案就在于,我們需要為每個(gè)Action準(zhǔn)備一個(gè)數(shù)據(jù)環(huán)境,這個(gè)數(shù)據(jù)環(huán)境被稱之為:
ActionContext。由于Action是應(yīng)對(duì)于一個(gè)又一個(gè)的URL請(qǐng)求,所以
ActionContext應(yīng)該具備以下的特性:
1. ActionContext應(yīng)成為Action與Web容器之間的橋梁 2. ActionContext中應(yīng)該保存有針對(duì)某個(gè)請(qǐng)求的詳細(xì)信息 3. ActionContext應(yīng)該是一個(gè)線程安全的類對(duì)象 Interceptor —— 豐富的層次結(jié)構(gòu) 簡(jiǎn)單回顧一下上面所提到的Action的職責(zé),我們看到,需要在Action這個(gè)層面上完成的事情還不少。而完成這些職責(zé),就需要我們對(duì)這些職責(zé)進(jìn)行合理的分類和排序,將他們組織成有序的執(zhí)行隊(duì)列。在Struts2中,使用了一種類似責(zé)任鏈的設(shè)計(jì)模式對(duì)這些不同的職責(zé)進(jìn)行分類并串聯(lián)起來,從而使得Action層具備了豐富的層次結(jié)構(gòu)。而在這個(gè)執(zhí)行隊(duì)列中的每個(gè)元素,就被我們稱之為Interceptor,也就是攔截器。
Struts2 Reference 寫道
Interceptors can execute code before and after an Action is invoked.
攔截器是AOP中的概念,它本身是一段代碼,可以通過定義“織入點(diǎn)”,來指定攔截器的代碼在“織入點(diǎn)”的前后執(zhí)行,從而起到攔截的作用。正如上面Struts2的Reference中講述的,Struts2的Interceptor,其攔截的對(duì)象是Action代碼,可以定義在Action代碼之前或者之后執(zhí)行攔截器的代碼。
如果仔細(xì)留意一下Action LifeCycle圖中的Interceptor和Action的部分,我們可以看到,Interceptor一層一層的把Action包了起來。這是一個(gè)典型的堆棧結(jié)構(gòu),在代碼執(zhí)行的時(shí)候,每個(gè)Interceptor不僅需要文成它自身的邏輯,還通過遞歸調(diào)用負(fù)責(zé)下一個(gè)攔截器或Action的調(diào)用。
Struts2 Reference 寫道
Most of the framework's core functionality is implemented as Interceptors. Features like double-submit guards, type conversion, object population, validation, file upload, page preparation, and more, are all implemented with the help of Interceptors. Each and every Interceptor is pluggable, so you can decide exactly which features an Action needs to support.
也正如Struts2的Reference所說,Struts2提供的絕大多數(shù)的功能支持,都通過Interceptor來實(shí)現(xiàn),這些Interceptor可以隨意進(jìn)行配置,并且能夠方便的插入到程序中去運(yùn)行。
Result —— 執(zhí)行結(jié)果 有執(zhí)行就必然有執(zhí)行的結(jié)果。在Struts2中,Action的執(zhí)行結(jié)果被抽象成了一個(gè)層次。在這個(gè)層次中,可以定義任意類型的View層的結(jié)構(gòu)。也就是說,Struts2并不強(qiáng)制View層的表現(xiàn)形式,可以是JSP、Freemarker模板、二進(jìn)制流輸出等等。
Struts2把執(zhí)行結(jié)果抽象成一個(gè)層次,使得你可以不再關(guān)注許多視圖整合上面的細(xì)節(jié),只需要考慮視圖的類型和數(shù)據(jù)的準(zhǔn)備,這樣,你也可以不必在沉浸在雜亂的構(gòu)造視圖的代碼中。
ActionProxy —— 執(zhí)行環(huán)境 有了攔截器Interceptor,有了Action本身,也有了Action的執(zhí)行結(jié)果Result,我們就需要一個(gè)類似調(diào)度器的產(chǎn)品,將這些元素整合起來,進(jìn)行調(diào)度執(zhí)行。在上面的Action Lifecyle的圖中,我們可以看到,Interceptor、Action和Result都處于ActionProxy中,所以ActionProxy就成為了所有這些元素的執(zhí)行環(huán)境。
既然是執(zhí)行環(huán)境,那么ActionProxy就需要提供Action執(zhí)行的時(shí)候一切所需要的配置、參數(shù)等等,當(dāng)然,也要有進(jìn)行Action調(diào)用的入口。所以讓我們來看一下ActionProxy的接口:
- public interface ActionProxy {
-
-
-
-
- void prepare() throws Exception;
-
-
-
-
- Object getAction();
-
-
-
-
- String getActionName();
-
-
-
-
- ActionConfig getConfig();
-
-
-
-
-
-
- void setExecuteResult(boolean executeResult);
-
-
-
-
- boolean getExecuteResult();
-
-
-
-
- ActionInvocation getInvocation();
-
-
-
-
- String getNamespace();
-
-
-
-
-
-
-
-
-
- String execute() throws Exception;
-
-
-
-
-
-
-
- void setMethod(String method);
-
-
-
-
- String getMethod();
-
- }
很顯然,在這其中,prepare和execute方法是用作Action調(diào)用的入口函數(shù),其他的接口定義都與Action執(zhí)行時(shí)的運(yùn)行參數(shù)和配置有關(guān)。
ActionInvocation —— 調(diào)度者 在上面的ActionProxy的接口定義中,我們可以看到有一個(gè)比較特殊的變量:
ActionInvocation比較吸引我們的眼球。從字面上去理解,ActionInvocation就是Action的調(diào)用者。事實(shí)上也是如此,ActionInvocation在這個(gè)Action的執(zhí)行過程中,負(fù)責(zé)Interceptor、Action和Result等一系列元素的調(diào)度。
在之后的章節(jié)中,這個(gè)ActionInvocation類也將成為我們解讀Struts2源碼的一個(gè)重要入手點(diǎn)。這個(gè)類將告訴你,Struts2是如何通過ActionInvocation來實(shí)現(xiàn)對(duì)Interceptor、Action和Result的合理調(diào)度的。