Struts 應(yīng)用轉(zhuǎn)移到 Struts 2
原文出處:
http://www.infoq.com/articles/converting-struts-2-part1
![]()
http://www.infoq.com/articles/migrating-struts-2-part2![]()
(本文已取得InfoQ.com翻譯授權(quán),譯者:cac@SpringSide團(tuán)隊(duì)
,???轉(zhuǎn)載請注明出處)
? ???有很多人都很熟悉 Struts, 無論是從項(xiàng)目中直接獲得的實(shí)戰(zhàn)經(jīng)驗(yàn)還是從書中了解到的。我們這一系列文章,將通過一個(gè)由 Stuts 轉(zhuǎn)移到 Struts2 簡單的例子向大家展現(xiàn)Struts2的所有特征。
? ? 在我們開始這個(gè)例子之前,你需要去知道一點(diǎn) Struts2的背景知識。 在第一部分的文章中,我們將介紹Struts2與Struts的核心框架的不同點(diǎn),以助于更好地了解其他方面的整合。第二部分中,我們將深入探討 actions 的差別, action相關(guān)的框架特征,和action配置。在最后一部分中,我們將會講述 user interface,我們也會講到其架構(gòu),UI構(gòu)件,themes 和標(biāo)簽。 還有如何為你的應(yīng)用加上新的外觀。
? ? 我們并不打算談及遷移過程的所有細(xì)節(jié)方面,我們只是從出發(fā)點(diǎn)開始介紹Struts2 的概念和現(xiàn)在可用的所有特征。但擁有這些知識,你將在以后Struts2的應(yīng)用中無往而不利。
? ?
Struts的歷史
? ? Struts的第一個(gè)版本 是在 2001年5月份發(fā)布。它提供了一個(gè)Web應(yīng)用的解決方案,如何讓 JSPs 和 servlets 共存去提供清晰的分離視圖和業(yè)務(wù)和應(yīng)用邏輯的架構(gòu)。在Struts之前,最通常的做法是在JSP中加入業(yè)務(wù)和應(yīng)用邏輯,或者在servlets中生成視圖。
? ? 自從第一個(gè)版本的發(fā)布, Struts 實(shí)際上已成為業(yè)界公認(rèn)的Web應(yīng)用標(biāo)準(zhǔn)。但隨著時(shí)間的推移,Web應(yīng)用框架經(jīng)常變化的需求,產(chǎn)生了幾個(gè)下一代 Struts的解決方案。其中兩個(gè)可選方案是Shale 和 Struts Ti。 Shale 是一個(gè)基于構(gòu)建的框架,并在最近成為 Apache 中的重要項(xiàng)目。而 Struts Ti 則是繼續(xù)堅(jiān)持 MVC模式的基礎(chǔ)上改進(jìn),繼續(xù)Struts的成功經(jīng)驗(yàn)。
? ? WebWork項(xiàng)目是在2002年3月發(fā)布的,它對Struts式框架進(jìn)行了革命性改進(jìn),引進(jìn)了不少新的思想,概念和功能,但和原Struts代碼并不兼容。WebWork是一個(gè)成熟的框架,經(jīng)過了好幾次重大的改進(jìn)與發(fā)布。在2005年12月,WebWork與Struts Ti決定合拼, 再此同時(shí), Struts Ti 改名為 Struts Action Framework 2.0,成為Struts真正的下一代。
請求如何運(yùn)作
? ? 在我們開始詳細(xì)探討如何轉(zhuǎn)移Struts到Struts2之前,讓我們來看看整個(gè)請求流程在新架構(gòu)中是如何運(yùn)作的。你會注意到在整個(gè)請求的生命周期,仍是以controller作主體,而且所有的概念還都是你以前所熟悉的, 就如:
- 通過URL請求的參數(shù)來調(diào)用Actions來把數(shù)據(jù)傳給server.
- 所有的Servlet objects (request, response, session,之類.) 仍然可以在Action中獲取
下圖展示了整個(gè)請求的概要過程:
整個(gè)請求過程可以分為六步驟:
- 一個(gè)請求產(chǎn)生并經(jīng)由框架處理 - 框架根據(jù)請求匹配相應(yīng)的配置,如使用哪些攔截器,action 類和結(jié)果。
- 請求通過一系列的攔截器 - 攔截器,和攔截器組經(jīng)配置后,能處理不同等級的請求,它們?yōu)檎埱筇峁┝烁鞣N預(yù)處理,切面處理。這和Struts的使用 Jakarta Commons Chain 構(gòu)件的 RequestProcessor類很相似。
- 調(diào)用 Action - 產(chǎn)生一個(gè)新的action對象實(shí)例,并提供請求所調(diào)用的處理邏輯的方法。Struts2 可以在配置action時(shí)為請求分配其指定的方法。我們在第二部文章中將對這步驟進(jìn)行進(jìn)一步討論;
- 調(diào)用產(chǎn)生的結(jié)果 - 獲取通過action的方法處理后返回來的結(jié)果,匹配其result class并調(diào)用產(chǎn)生的實(shí)例。有種情況是在UI模板去生成HTML時(shí)才去處理這些結(jié)果。如果在這種情況下,在Struts2 模板中的tags能直接返回到 action 中,取結(jié)果來呈現(xiàn)界面。
- 請求再次經(jīng)過一系列的攔截器處理后返回 - 請求反順序通過與原來進(jìn)入時(shí)的攔截器鏈, 當(dāng)然,你也可以配置在這個(gè)過程中減少或增加攔截器處理.
- 請求返回到用戶 - 最后一步是由 control 返回到servlet。通常是生成HTML返回到user, 但你也可以指定返回的HTTP頭或HTTP重定向。
你應(yīng)該已注意到,Struts2與Struts的差別。最明顯的就是Struts2是pull-MVC 架構(gòu),就是可以直接從Action中獲取所需要的數(shù)據(jù),而不是像Struts那樣必須把 beans 存到page, request,或者session中才能獲取。這個(gè)我們將在下一章中詳細(xì)提及。
配置框架
首先最重要的是,讓框架能通過web.xml在servlet containers里運(yùn)行。
下面這個(gè)就是大家都熟悉的 Struts在 web.xml里的配置方法
<servlet>
? ?? ???<servlet-name>action</servlet-name>
? ?? ???<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
? ?? ???<init-param>
? ?? ?? ?? ?<param-name>config</param-name>
? ?? ?? ?? ?<param-value>/WEB-INF/struts-config.xml</param-value>
? ?? ???</init-param>
? ?? ???<load-on-startup>2</load-on-startup>
? ? </servlet>
? ? <servlet-mapping>
? ?? ???<servlet-name>action</servlet-name>
? ?? ???<url-pattern>*.do</url-pattern>
? ? </servlet-mapping>
在 Struts2 中,這個(gè)有少許改變,最明顯的是dispatcher 由servlet轉(zhuǎn)為servlet filter, 其配置和servlet一樣簡單,如下:
?? <filter> ? ?? ??? <filter-name>webwork</filter-name> ? ?? ??? <filter-class> ? ?? ?? ?? ?org.apache.struts.action2.dispatcher.FilterDispatcher </filter-class> ? ? </filter> ? ? <filter-mapping> <filter-name>webwork</filter-name> <url-pattern>/*</url-pattern> ? ? </filter-mapping>
和servlet配置一樣,filter配置定義了名稱(供關(guān)聯(lián))和filter的類。filter mapping讓URI匹配成功的的請求調(diào)用該filter。默認(rèn)情況下,擴(kuò)展名為".action"。
這個(gè)是在default.properties文件里的"struts.action.extension" 屬性定義的。
工具箱:??"default.properties"是配置選項(xiàng)定義文件。通過在classpath中包含一個(gè)叫"struts.properties"的文件,并設(shè)置不同的屬性值,你可以覆蓋這個(gè)默認(rèn)的配置,實(shí)現(xiàn)自己的配置。
對于Struts, servlet配置提供了初始化tag的參數(shù)和使用的文件,而Struts2沒有這樣的配置參數(shù),取而代之的是在classpath下的默認(rèn)配置文件"struts.xml"。
工具箱/提示: Struts actions(擴(kuò)展名".do"),Struts2 actions(擴(kuò)展名".action"),所以Struts和Struts2可以在一個(gè)系統(tǒng)中共存。所以最好是保持原先的系統(tǒng),在新功能的開發(fā)上用 Struts2, 如果時(shí)間和資源允許的情況下再逐步遷移。另一種方法是只是把Struts2的擴(kuò)展名改為".do",可重用JSPs.
分析Actions
在上面介紹的請求運(yùn)作流程中,我們談及了一些Struts和Struts2的不同點(diǎn)。現(xiàn)在我們將較深入地探討這兩個(gè)框架中action結(jié)構(gòu)的具體差別。
讓我們來回顧一下 Struts 的 action 結(jié)構(gòu), 主要的形式如下:
public class MyAction extends Action { ? ? public ActionForward execute(ActionMapping mapping, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?ActionForm form, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?HttpServletRequest request, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?HttpServletResponse response) ? ?? ?? ?? ?throws Exception { ? ?? ???// do the work ? ?? ???return (mapping.findForward("success")); ? ? } }
當(dāng)實(shí)現(xiàn)一個(gè)Struts action時(shí), 你需要注意一下問題:
- 所有的action 都必須繼承于base Action 類.
- 所有的action都必須是線程安全的,因?yàn)閍ction是單例的,singleton的.
- 因?yàn)樗械腶ction都必須是線程安全的,所以所有的對象都不能是類屬性, 都必須以方法參數(shù)的形式傳值。
- 調(diào)用action的方法必須命名為 "execute" ( 在Struts中的??DispatchAction 類好像可以用其它方法去執(zhí)行同一個(gè)action ,但實(shí)際上在框架中調(diào)用的仍然是 "execute" 方法。).
- ActionForward 的結(jié)果是通過ActionMapping 類中的方法來產(chǎn)生的,通常是"findForward"方法.
相比較之下, Struts2的action 提供了很多簡單的實(shí)現(xiàn)。下面就是個(gè)例子
public class MyAction { ? ?publicString execute() throws Exception { ? ?? ???// do the work ? ?? ???return"success"; ? ?} }
首先你會注意到的是,Struts2中的action不再繼承于任何類或需要實(shí)現(xiàn)任何接口。實(shí)際上,它還遠(yuǎn)不只這些。按照慣例,只有"execute"方法能調(diào)用action, 但在Struts2中并非必要,任何聲明為public String methodName() 方法都能通過配置來調(diào)用action.
另外,你會注意到返回值不再是"ActionForward ",而是String, 如果你需喜歡String的形式,那在Action接口里有個(gè)幫助方法可以提供簡單的結(jié)果常量,如"success", "none", "error", "input" 和 "login"。
最后,和Struts最大的革命性的不同是, 調(diào)用action不再是帶參數(shù)的。那你如何在獲得你所需要的值呢?答案是"inversion of control" 或 "dependency injection", 反轉(zhuǎn)控制(想了解更多可以看Martin Fowler的文章 http://www.martinfowler.com/articles/injection.html)。
為了更好地了解反轉(zhuǎn)控制,讓我們來看看一個(gè)例子,如何在action處理過程中可以訪問到HttpServerRequest 。在我們的例子中,我們用ServletRequestAware 接口,這個(gè)接口包含了相應(yīng)屬性的setter,如下
public interface ServletRequestAware { ? ? public void setServletRequest(HttpServletRequest request); }
當(dāng)我們繼承這個(gè)接口時(shí),我們需要通過setter為我們的HttpServerRequest 屬性變量賦值:
public class MyAction implements ServletRequestAware { ? ?private HttpServletRequest request; ? ?public void setServletRequest(HttpServletRequest request) { ? ?? ???this.request = request; ? ?} ? ?publicString execute() throws Exception { ? ?? ???// do the work using the request ? ?? ???return Action.SUCCESS; ? ?} }
看起來現(xiàn)在這些屬性是類級別的,并不是線程安全的,但是在Struts2里并沒有問題,因?yàn)槊總€(gè)請求過來的時(shí)候都會產(chǎn)生一個(gè)新的action對象實(shí)例,它并沒有和其他請求共享一個(gè)對象,所以不需要考慮線程安全問題。
現(xiàn)在我們還有最后一步,就是把a(bǔ)ction關(guān)聯(lián)上ServletConfigInterceptor 攔截器。這個(gè)攔截器繼承了ServletRequestAware 接口,并提供了把HttpServletRequest 注入到action中的功能。但是你現(xiàn)在不用擔(dān)心如何配置這些,我們將在下一篇文章中具體講述。最重要的是我們明白了攔截器和接口共同為action提供了反轉(zhuǎn)控制的功能。
這個(gè)設(shè)計(jì)的好處是能讓action完全和框架解耦。action僅僅是一個(gè)被框架使用的簡單的POJO。這對于單元測試但來極大的好處, 你能方便的為Struts action實(shí)現(xiàn) StrutsTestCase 或??MockStrutsTestCase 單元測試。
總結(jié)
By到現(xiàn)在為止,你應(yīng)該已經(jīng)了解了Struts2的整個(gè)請求流程,還有高層的框架概念, 你也應(yīng)該能自己動(dòng)手配置Struts2的action,和講出Struts和Struts2的差別了。
在下篇文章中,我們將會介紹一個(gè)詳細(xì)的Struts應(yīng)用向Struts2遷移的例子,同時(shí)我們也會介紹遷移中相關(guān)的知識,會講述如何綜合使用JSTL, JSP 和 Struts2,進(jìn)一步講述Struts和Struts2的action的差別,Struts2的配置和其他框架元素,和談到更多的其他相關(guān)框架的特征。
Struts 應(yīng)用轉(zhuǎn)移到 Struts 2 ( 二 )
??? 在上篇文章中,我們已經(jīng)從較高層解釋了整個(gè)框架的結(jié)構(gòu),請求流程的基礎(chǔ),配置方式和Struts2和Struts1的不同之處。了解這些后從Struts 應(yīng)用 遷移到 Struts 2 不再是難事。
? ? 在這篇文章中,我們將會更詳細(xì)地講述如何由Struts 的action轉(zhuǎn)為Struts 2的action。
一個(gè)應(yīng)用的例子
這個(gè)例子選擇了大家都熟悉的 - weblog. 簡單地介紹下這例子的功能需求:
- 增加一個(gè)新的日志
- 察看一個(gè)日志
- 修改一個(gè)日志
- 刪除一個(gè)日志
- 列出所有日至
? ???增刪修改(CRUD),是項(xiàng)目中最為普遍的應(yīng)用。
? ???業(yè)務(wù)邏輯類在Struts 和 Struts2 應(yīng)用都是可共用的。如:
public class BlogService { ? ???privatestatic List<Blog> blogs = new ArrayList<Blog>(); ? ???public List<Blog> list() { ... } ? ???public Blog create(Blog blog) { ... } ? ???public void update(Blog blog) { ... } ? ???public void delete(int id) { ... } ? ???public Blog findById(int id) { ... } }?
?????BlogService 只是個(gè)簡單的業(yè)務(wù)邏輯類,并不是接口,Struts 和 Struts2 的action皆可調(diào)用其實(shí)例。雖然這樣設(shè)計(jì)在實(shí)際項(xiàng)目中會帶來不必要的耦合,但我們的例子只是集中在討論web層上,所以無關(guān)重要。
工具箱: 在第一篇文章中,我們談?wù)摿嗽赟truts2 actions中的依賴注入的接口注入方式。這個(gè)是servlet 相關(guān)類(HttpServletRequest, HttpServletResponse, PrincipalProxy, 等.)的主要注入方式,但這并不是唯一的方式。
Struts2 可以使用Spring框架作為默認(rèn)的容器時(shí),依賴注入的setter方法就可用了。通過在action中加入setter方法(如下演示), Struts2 框架將能從Spring框架中獲得正確的信息,并通過setter加載在action中。
public void setBlogService(BlogService service) { ? ???this.blogService = service; }
和接口注入方式類似,我們需要一個(gè)攔截器來幫助我們完成任務(wù),這就是 ActionAutowiringInterceptor 攔截器。這樣我們的業(yè)務(wù)邏輯類就通過Spring框架管理自動(dòng)在action被調(diào)用之前注入到Struts2得action中。有多種的配置參數(shù)(如by name, by type 或 automatically)可供選擇,可以讓對象和setter匹配的注入的方式根據(jù)你的需要而定。
Struts 應(yīng)用中的代碼
? ???我們首先從Struts講起。在Struts中,最普遍的做法是,對于每個(gè)需求用例(如save,update,remove,list)來說都會有對應(yīng)的action類,同時(shí)也會有相應(yīng)的action form類。在我們的應(yīng)用中的這個(gè)方式或許不是最佳的實(shí)現(xiàn)方式(其他的解決方案包括使用dynamic form或者使用request來分發(fā)action),但我們例子中的做法是所有Struts開發(fā)者最熟悉的一種形式。了解了這種簡單的實(shí)現(xiàn)方法,你有能力在遷移到Struts2時(shí),使用更為優(yōu)秀的方法。
在第一篇文章中我們談過Struts 和 Struts2 中action的區(qū)別。現(xiàn)在我們從UML中再次看看他們的差別。一般來說form在Struts action中的表現(xiàn)形式是:
這action form將會在多個(gè)action中使用,讓我們來看看它:
public class BlogForm extends ActionForm { ? ???privateString id; ? ???privateString title; ? ???privateString entry; ? ???privateString created; ? ???// public setters and getters for all properties }
?????如UML中展示的那樣,其中一個(gè)限制就是必須繼承ActionForm類,另外一個(gè)限制就是form中所有屬性都必須是String類型,所以所有的getter和setter都必須只能接受String參數(shù)和返回String結(jié)果。
然后我們來看看action。我們這個(gè)例子中的action有view, create 和 update action。
The View Action:
public class ViewBlogAction extends Action { ? ???public ActionForward execute(ActionMapping mapping, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ActionForm form, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletRequest request, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletResponse response) ? ?? ?? ?? ? throws Exception { ? ?? ?? ?BlogService service = new BlogService(); ? ?? ?? ?String id = request.getParameter("id"); ? ?? ?? ?request.setAttribute("blog",service.findById(Integer.parseInt(id))); ? ?? ?? ? return (mapping.findForward("success")); ? ???} }
The Create Action:
public class SaveBlogEntryAction extends Action { ? ?? ?public ActionForward execute(ActionMapping mapping, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ActionForm form, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletRequest request, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletResponse response) ? ?? ?? ?? ? throws Exception { ? ?? ?? ?BlogService service = new BlogService(); ? ?? ?? ?BlogForm blogForm = (BlogForm) form; ? ?? ?? ?Blog blog = new Blog(); ? ?? ?? ?BeanUtils.copyProperties( blog, blogForm ); ? ?? ?? ?service.create( blog ); ? ?? ?? ?return (mapping.findForward("success")); ? ???} }
The Update Action:
public class UpdateBlogEntryAction extends Action { ? ???public ActionForward execute(ActionMapping mapping, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ActionForm form, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletRequest request, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletResponse response) ? ?? ?? ?? ? throws Exception { ? ?? ?? ?BlogService service = new BlogService(); ? ?? ?? ?BlogForm blogForm = (BlogForm) form; ? ?? ?? ?Blog blog = service.findById( Integer.parseInt(blogForm.getId())); ? ?? ?? ?BeanUtils.copyProperties( blog, blogForm ); ? ?? ?? ?service.update( blog ); ? ?? ?? ?request.setAttribute("blog",blog); ? ?? ?? ?return (mapping.findForward("success")); ? ???} }
這三個(gè)action都跟隨著同一個(gè)模式:
- 產(chǎn)生一個(gè)新的業(yè)務(wù)邏輯對象實(shí)例 - 如前面所提到的,我們使用最直接的方式在action中使用業(yè)務(wù)邏輯對象,這表示在每個(gè)action中都會產(chǎn)生新的業(yè)務(wù)邏輯對象實(shí)例。
- 從請求中獲得數(shù)據(jù) - 這是兩種形式之一。在view action中,"id"是從HttpServletRequest 對象中直接獲取的。而在create 和 update action 中,則從ActionForm 中取值。ActionForm 與 HttpServletRequest 的調(diào)用方式其實(shí)很相似,唯一不同的ActionForm 是bean的從field中取值。
- 調(diào)用業(yè)務(wù)邏輯 - 現(xiàn)在開始生成調(diào)用業(yè)務(wù)邏輯所需的參數(shù)并調(diào)用邏輯。如果參數(shù)(在view action中)是一個(gè)簡單對象類型,則轉(zhuǎn)換值時(shí)會自動(dòng)轉(zhuǎn)為正確的類型(如從String轉(zhuǎn)到Integer)。如果參數(shù)是復(fù)雜的對象類型,,則ActionForm 需要通過BeanUtil 來幫忙轉(zhuǎn)成相應(yīng)的對象。
- 設(shè)定返回的數(shù)據(jù) - 如果需要把數(shù)據(jù)返回顯示給用戶,那則要把這個(gè)數(shù)據(jù)設(shè)在HttpServletRequest 的attribute 中返回。
- 返回一個(gè) ActionForward - 所有 Struts action的最后都需要找到并返回其相應(yīng)的 ActionForward 對象.
? ???最后的兩個(gè)action,remove和list action, 只有很少的差別。remove action如下所示,沒有用BlogForm類. 通過從request的attribute中獲取"id"(和view action相似),就能調(diào)用業(yè)務(wù)邏輯完成其需要的工作。在下面我們介紹配置時(shí),你可以看到它并沒有返回任何數(shù)據(jù),因?yàn)樗?success"返回結(jié)果其實(shí)是執(zhí)行remove后再執(zhí)行了list action來返回信息的。
public class RemoveBlogEntryAction extends Action { ? ???public ActionForward execute(ActionMapping mapping, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ActionForm form, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletRequest request, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletResponse response) ? ?? ?? ?? ? throws Exception { ? ?? ?? ?BlogService service = new BlogService(); ? ?? ?? ?String id = request.getParameter("id"); ? ?? ?? ?service.delete(Integer.parseInt(id)); ? ?? ?? ?return (mapping.findForward("success")); ? ???} }
?list action并不需要任何的用戶輸入,它只是簡單地調(diào)用了業(yè)務(wù)邏輯的無參方法,同時(shí)返回所有的Blog對象。
public class ListBlogsAction extends Action { ? ???public ActionForward execute(ActionMapping mapping, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ActionForm form, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletRequest request, ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? HttpServletResponse response) ? ?? ?? ?? ? throws Exception { ? ?? ?? ?BlogService service = new BlogService(); ? ?? ?? ?request.setAttribute("bloglist",service.list()); ? ?? ?? ?return (mapping.findForward("success")); ? ???} }
向 Struts2 遷移
? ???在Struts2中,可選的實(shí)現(xiàn)方式有很多,可以像Struts那樣每個(gè)需求用例對應(yīng)一個(gè)action,也可以用一個(gè)action對應(yīng)所有需求用例。但在我們的例子中,使用的方法是我認(rèn)為最佳的解決方案 - 在一個(gè)action類中實(shí)現(xiàn)整套CRUD功能。
? ???也許你人為把list需求用例也同樣地整合到同一個(gè)action類里會比較好,而我認(rèn)為把list的功能分到另外一個(gè)action中,會減少容易產(chǎn)生的混淆,因?yàn)閘ist用例中并不需要Blog這個(gè)類作為屬性,而在其他用例中則需要。
對于 Struts2的例子, 它的UML模型展示如下:
? ???每個(gè)用例在action中都有自己所對應(yīng)的方法。從上圖中我們可以看到,在BlogAction 中我們有save, update 和 remove三個(gè)方法。而ListBlogAction中,沒有l(wèi)ist這個(gè)方法,因?yàn)?b>ListBlogAction繼承了ActionSupport 類,實(shí)際上就是在默認(rèn)的execute 方法中實(shí)現(xiàn)list功能。
? ???為了更容易看,圖中的BlogAction并沒有畫出它所實(shí)現(xiàn)了的三個(gè)接口。它們分別是ServletRequestAware 接口,??Prepareable 接口和 ModelDriven 接口。
? ???首先回顧一下ServletRequestAware, 我們在第一篇文章中已經(jīng)詳細(xì)介紹它了。這個(gè)ParametersInterceptor 攔截器提供了把HttpServletRequest 自動(dòng)set到action中的功能,讓我們能通過request, 把所需的值傳回到JSPs。
? ???接著看看Preparable 接口, 它會聯(lián)合PrepareInterceptor攔截器一起工作,讓action在執(zhí)行execute() 方法前, 執(zhí)行一個(gè)prepare()方法,實(shí)現(xiàn)在執(zhí)行前設(shè)定,配置或預(yù)設(shè)一些值在action中。 在我們的例子里,prepare方法會檢查blogId 屬性,如果為零則這是一個(gè)新日志,非零則該日志已經(jīng)存在,根據(jù)blogId取出日志。
? ???最后我們說說ModelDriven 接口,在上一篇文章中,我們已經(jīng)了解到 Struts action的很大的不同在于它是需要線程安全的,而在Struts2中則沒有這個(gè)限制,因?yàn)槊看蔚恼埱蠖紩幸淮蝍ction對象的初始化和調(diào)用。沒有了這個(gè)限制,能允許Struts2使用類級別的屬性變量(特別是getters和setters),從而獲得更多編碼優(yōu)勢。
和攔截器的功能結(jié)合起來, 把HttpServletRequest 中的attribute 注入action中的流程如下所示:
- 循環(huán)讀取HTTP request中的attribute
- 查找當(dāng)前request attribute中是否有和action中的setter中的屬性匹配的
- 有則根據(jù)attribute從HttpServletRequest 里取出其值
- 把取出來的值由String轉(zhuǎn)成setter中相應(yīng)的類型
- 調(diào)用setter把該轉(zhuǎn)換后的值注入action中
提示: 當(dāng)調(diào)用action時(shí),如果發(fā)現(xiàn)不明原因使不能正確地通過setter注入值情況下,第一步最好是先檢查下各攔截器,確保它們都已作用于該action。因?yàn)檫@些意外通常有時(shí)由攔截器設(shè)置不當(dāng)形成的,檢查是否各個(gè)攔截器都已起作用,并看看它們作用的順序,因?yàn)橛行┣闆r下它們間會相互影響而產(chǎn)生錯(cuò)誤。
? ? 現(xiàn)在我們已經(jīng)有基于String類型的form bean中取值的方法或者是自動(dòng)把request的attributes 注入到action的方法,那下一步就是如何把值傳入 domain object 或 value / transfer object的屬性中去。其實(shí)這很簡單,你只需要實(shí)現(xiàn)ModelDriven 接口(即實(shí)現(xiàn)getModel()*方法)就可以了,確保ModelDrivenInterceptor* 攔截器已作用于action。
? ? 除了會調(diào)用action中的setter外,model 首先檢查是否有和setter可以匹配當(dāng)前的attribute名。如果在model中沒有這個(gè)attribute相應(yīng)的setter,則會再在action上找相應(yīng)的setter來設(shè)值。
? ? 在BlogAction 的例子中我們可以看到如何很靈活地使用這些方法,首先通過prepare() 方法根據(jù)Id獲取相應(yīng)的 Blog model object 或新建一個(gè)instance, 然后再根據(jù)把request中相應(yīng)的屬性注入Blog instance中和action中。
? ? 以上的兩個(gè)功能使得現(xiàn)在調(diào)用action那么簡單 - 調(diào)用具體的業(yè)務(wù)邏輯,和把數(shù)據(jù)設(shè)在HttpServletRequest供返回用*。*
public class BlogAction extends ActionSupport ? ?? ?? ?implements ModelDriven, Preparable, ServletRequestAware { ? ???privateint blogId; ? ???private Blog blog; ? ???private BlogService service = new BlogService(); ? ???private HttpServletRequest request; ? ???public void setServletRequest(HttpServletRequest httpServletRequest) { ? ?? ?? ?this.request = httpServletRequest; ? ???} ? ?? ?public void setId(int blogId) { ? ?? ?? ?this.blogId = blogId; ? ???} ? ?? ?public void prepare() throws Exception { ? ?? ?? ?if( blogId==0 ) { ? ?? ?? ?? ? blog = new Blog(); ? ?? ?? ?} else { ? ?? ?? ?? ? blog = service.findById(blogId); ? ?? ?? ?} ? ???} ? ?? ?publicObject getModel() { ? ?? ?? ?return blog; ? ???} ? ?? ?publicString save() { ? ?? ?? ?service.create(blog); ? ?? ?? ?return SUCCESS; ? ???} ? ?? ?publicString update() { ? ?? ?? ?service.update(blog); ? ?? ?? ?request.setAttribute("blog",blog); ? ?? ?? ?return SUCCESS; ? ???} ? ?? ?publicString remove() { ? ?? ?? ?service.delete(blogId); ? ?? ?? ?return SUCCESS; ? ???} ? ?? ?publicString execute() { ? ?? ?? ?request.setAttribute("blog",blog); ? ?? ?? ?return SUCCESS; ? ???} }?
??? 最后就是說說 list這個(gè)用例了。它同樣需要訪問HttpServletRequest對象去返回?cái)?shù)據(jù)給JSP,所以也需要實(shí)現(xiàn)ServletRequestAware 接口。但是,因?yàn)樗⒉恍枰魏屋斎胫担跃筒恍枰獙?shí)現(xiàn)其他的接口了。以下是它的具體實(shí)現(xiàn):
public class ListBlogsAction extends ActionSupport implements ServletRequestAware { ? ???private BlogService service = new BlogService(); ? ???private HttpServletRequest request; ? ???public void setServletRequest(HttpServletRequest httpServletRequest) { ? ?? ?? ?this.request = httpServletRequest; ? ???} ? ???publicString execute() { ? ?? ?? ?request.setAttribute("bloglist",service.list()); ? ?? ?? ?return SUCCESS; ? ???} }
這樣就完成了我們該實(shí)現(xiàn)的action代碼了。 在下一篇文章中,當(dāng)我們新的Struts2用戶界面結(jié)合時(shí),我們還會進(jìn)一步簡化action的代碼。
配置Actions
? ? 在我們調(diào)用action之前,我們必須通過XML配置文件去配置它們。
? ? 在Struts中, 我們習(xí)慣用在WEB-INF 目錄的"struts-config.xml"配置文件,在這里我們需要配置action form和action屬性。在Struts2中, 用的是在classpath中的"struts.xml"配置文件, 它看起來好象會稍微復(fù)雜一些,因?yàn)樗枰谂渲胊ction的同時(shí)也配置其攔截器。
? ? 在Struts中配置 form-beans 節(jié)點(diǎn)很容易, 只需要一個(gè)唯一的名字,還有就是繼承ActionForm類的class作為type。
<struts-config> ? ???<form-beans> ? ?? ?? ?<form-bean name="blogForm" ? ? type="com.fdar.articles.infoq.conversion.struts.BlogForm"/> ? ???</form-beans> ? ???... </struts-config>
在我們的例子中,我們的配置文件有3點(diǎn)不相同:
1. 重定向配置
? ? 在Struts的配置中,每個(gè)mapping 都需要提供調(diào)用action時(shí)所需要對應(yīng)的路徑,Struts默認(rèn)為".do", 例如paht是"/struts/add"對應(yīng)于URL"/struts/add.do"。同時(shí)也需要一個(gè)forward 屬性來提供給URL去轉(zhuǎn)向,如"/struts/add.jsp".
<struts-config> ? ???... ? ?? ?<action-mappings> ? ?? ?? ? <action path="/struts/add" forward="/struts/add.jsp"/> ? ?? ?? ?... ? ?? ?</action-mappings> </struts-config>
而Struts2的需要更多的一些配置,如:
<struts> ? ?? ?<include file="struts-default.xml"/> ? ?? ?<package name="struts2"extends="struts-default" namespace="/struts2"> ? ?? ?? ? <action name="add" > ? ?? ?? ?? ? <result>/struts2/add.jsp</result> ? ?? ?? ?</action> ? ?? ?? ?... ? ?? ?</package> </struts>?
??? 首先你會注意到的是,代替action-mappings 節(jié)點(diǎn)的是include 和package 節(jié)點(diǎn)。Struts2可以把配置細(xì)分到任意數(shù)目的配置文件中,來實(shí)現(xiàn)配置可模塊化管理。每個(gè)配置文件的結(jié)構(gòu)其實(shí)都是一樣的,不同的只是文件名。
? ? include 節(jié)點(diǎn)中,以文件名作為file 屬性,可把所include的文件內(nèi)容包含到當(dāng)前文件中。
? ?package 節(jié)點(diǎn)把a(bǔ)ctions組成一組,其name 屬性的值必須是唯一的。
? ?在 Struts action的配置中, paht屬性需要指定完整的URL路徑。而在Struts2中,URL是通過package節(jié)點(diǎn)中的namespace屬性,還有在action 節(jié)點(diǎn)中的name 屬性, 和action擴(kuò)展(默認(rèn)是".action")共同起作用的。在上面的例子中,則URL為"/struts2/add.action"時(shí)會調(diào)用action。
? ?package節(jié)點(diǎn)除了可以分離命名空間外, package 節(jié)點(diǎn)中的 extends 屬性,還提供了某種可復(fù)合的組成結(jié)構(gòu)。通過繼承另外一個(gè)package節(jié)點(diǎn),你就能繼承那個(gè)節(jié)點(diǎn)的配置,包括其actions, results, interceptors, exception,等值。在我們的例子中,"struts2" package節(jié)點(diǎn)繼承了 "struts-default" package 節(jié)點(diǎn)(在"struts-default.xml" 文件里定義了該節(jié)點(diǎn)) ,注意這個(gè)是主要的include文件,所以必須在所有配置之前的第一行中寫出。 這個(gè)功能有助于大大減少你重復(fù)性輸入默認(rèn)配置所浪費(fèi)的時(shí)間。
? ? 最后是result 節(jié)點(diǎn), 它只是存放你這個(gè)action所需要轉(zhuǎn)向的URL. 在這里我們沒有提及name 和 type 屬性。如果你不想改變它們的默認(rèn)屬性的話,你能忽略不寫它們,讓你的配置文件看起來更清晰。從action返回的 "success" 的結(jié)果將組成這個(gè)JSP顯示給用戶。
2. Action 配置
? ? 在Struts 中forward 節(jié)點(diǎn)指定了action處理后,結(jié)果將重定向到哪個(gè)相應(yīng)的頁面。type屬性指定了action的類,scope 屬性保證了form beans只在request范圍內(nèi)。
<struts-config> ? ???... ? ?? ?<action-mappings> ? ?? ?? ? <action path="/struts/list" scope="request" ? ?? ?? ?? ?? ???type="com.fdar.articles.infoq.conversion.struts.ListBlogsAction" > ? ?? ?? ?? ? <forward name="success" path="/struts/list.jsp"/> ? ?? ?? ?</action> ? ?? ?? ?... ? ?? ?</action-mappings> </struts-config>
??? Struts2 的 XML配置和上面提到的基本相同。唯一不同的就是通過class屬性為action節(jié)點(diǎn)提供了它所需要調(diào)用的類的完整路徑
<struts> ? ???... ? ?? ?<package name="struts2"extends="struts-default" namespace="/struts2"> ? ?? ?? ? <default-interceptor-ref name="defaultStack"/> ? ?? ?? ? <action name="list" ? ?? ?? ?? ?? ???class="com.fdar.articles.infoq.conversion.struts2.ListBlogsAction"> ? ?? ?? ?? ? <result>/struts2/list.jsp</result> ? ?? ?? ?? ? <interceptor-ref name="basicStack"/> ? ?? ?? ?</action> ? ?? ?? ?... ? ?? ?</package> </struts>
??? 如果是用其他的方法而不是用默認(rèn)的execute 方法去調(diào)用action(在BlogAction 類中大多數(shù)方法如此), 則需要在action節(jié)點(diǎn)的 method 屬性里加入方法名,下面就是個(gè)例子,這時(shí)候update方法將會被調(diào)用。
<action name="update" method="update" ? ? class="com.fdar.articles.infoq.conversion.struts2.BlogAction" > ? ?? ???... ? ? </action>
??? default-interceptor-ref 和 interceptor-ref 節(jié)點(diǎn)有幾點(diǎn)不同。在第一篇文章中,我們看到在action被調(diào)用之前必須通過一系列的攔截器,而這兩個(gè)節(jié)點(diǎn)就是用來配置攔截器組的。default-interceptor-ref 節(jié)點(diǎn)為該package提供了默認(rèn)的攔截器組。當(dāng)在action節(jié)點(diǎn)中提供 interceptor-ref節(jié)點(diǎn)時(shí) ,它就會覆蓋默認(rèn)的攔截器(interceptor-ref 節(jié)點(diǎn)能夠和單獨(dú)一個(gè)攔截器相關(guān)聯(lián),或者跟一個(gè)攔截器組相關(guān)聯(lián)),在action節(jié)點(diǎn)中可以存在多個(gè)interceptor-ref節(jié)點(diǎn),處理攔截器組的順序會和該節(jié)點(diǎn)列出的順序一致。
3. 再重定向配置
? ? 當(dāng)我們提交表格的時(shí)候,我們需要重定向到更新后的結(jié)果頁面。這個(gè)通常稱為 "post-redirect pattern" 或, 最近出現(xiàn)的, "flash scope."
? ? 由于這是一個(gè)form, 所以在Struts中我們需要為Struts指定一個(gè)ActionForm。需要在name屬性中提供form的名稱,同樣地,我們也需要在forward 節(jié)點(diǎn)中舉加入redirect屬性為true。
<struts-config> ? ???... ? ?? ?<action-mappings> ? ?? ?? ? <action path="/struts/save" ? ?? ?? ?? ?? ???type="com.fdar.articles.infoq.conversion.struts.SaveBlogEntryAction" ? ?? ?? ?? ?? ???name="blogForm" scope="request"> ? ?? ?? ?? ? <forward name="success" redirect="true" path="/struts/list.do"/> ? ?? ?? ? </action> ? ?? ?? ?... ? ?? ?</action-mappings> </struts-config>
Struts2 在result 節(jié)點(diǎn)里提供了type 屬性, 默認(rèn)情況下是"dispatch", 如果需要重定向,則需要設(shè)為 "redirect"。
<struts> ? ???... ? ?? ?<package name="struts2"extends="struts-default" namespace="/struts2"> ? ?? ?? ? <action name="save" method="save" ? ?? ?? ?? ?? ???class="com.fdar.articles.infoq.conversion.struts2.BlogAction" > ? ?? ?? ?? ? <result type="redirect">list.action</result> ? ?? ?? ?? ? <interceptor-ref name="defaultStack"/> ? ?? ?? ? </action> ? ?? ?? ?... ? ?? ?</package> </struts>
總結(jié)
? ? 我們并不可能在這篇文章中覆蓋所有的內(nèi)容,如果你需要更好的了解整個(gè)框架,還有其他的實(shí)現(xiàn)方式和選項(xiàng),這里有幾點(diǎn)可以供你參考:
- 配置攔截器和攔截器組 - 以Struts2-core JAR 包里的"struts-default.xml" 文件作為例子。"struts-default.xml" 演示了如何配置你自己的攔截器組,包含新的攔截器,你可以嘗試實(shí)現(xiàn)自己的攔截器。
- 配置文件中的通配符模式 - 你可以選擇使用Struts2中的通配符模式來簡化你的配置。
- 通過 ParameterAware 接口把form值傳入maps中 - 你可以在Struct2中配置,讓所有request的form屬性都存于action的一個(gè)map中,這樣就不需要專門再為action指定model / transfer / value object了。這和Struts的dynamic form特點(diǎn)很相似。
? ? 也許到現(xiàn)在為,也許你有個(gè)疑問,"遷移后我們的界面是否可以完全重用呢?",答案是yes。你能從這里, 下載到我這篇文章中的完整源代碼,你可以自己嘗試把URL的擴(kuò)展名由".do" 改為 ".action",使用的頁面時(shí)一樣的。除此之外,其實(shí)用JSTL來代替Struts taglib也是很容易的。
在下一篇文章中,我們將講述用戶界面,討論themes 和 tags; 如何做validation;??如何重用UI控件。