[摘]主題: Struts2中的零配置與CoC(Convention over Configration)

摘要:介紹Struts2中的零配置(Zero Configuration),以及如何用COC來更好地簡化Struts2的配置。在第一章,我使用Maven來創建一個起點項目;第二章,以該項目爲例,講解如何使用Struts2的零配置;第三章,論述第二章中的實現方式的缺陷,然後講解如何使用COC來改進這些缺陷,並進一步簡化Struts2的配置。附件是這篇文章用到的示例代碼。

一、從零開始

這裏,我將建立一個新的示例項目,作爲講解的起點。我使用JDK 6、Maven 2、Eclipse 3.3來建立這個示例,如果讀者對Maven2不熟也沒關係,這只是個示例。
首先,運行下邊的命令:
                mvn archetype:create -DgroupId=demo.struts -DartifactId=demo-struts-coc -DarchetypeArtifactId=maven-archetype-webapp
這會建立如下的目錄結構:
 |- POM.xml
 |- src
     |- main
         |- resources
         |- webapp
             |- index.jsp
             |- WEB-INF
                 |- web.xml
然後我們在src/main目錄下新建一個名爲java的目錄,用來放置java代碼。在src下建立test目錄,並在test目錄下建立java目錄,用來放置測試代碼。另外,我這個示例不想使用JSP,所以我將src/main/webapp目錄下的index.jsp改爲index.html。
現在,需要配置該項目要用到哪些lib。在POM.xml中加入struts2-core:
xml 代碼
 
  1. <dependency>  
  2.     <groupId>org.apache.struts</groupId>  
  3.     <artifactId>struts2-core</artifactId>  
  4.     <version>2.0.9</version>  
  5. </dependency>  

另外,我想在Eclipse裏使用jetty來啓動項目並進行測試,所以在POM.xml中再加入jetty、jetty-util、servlet-api等的依賴,詳情見附件。
我希望使用Eclipse來作爲這個項目的IDE,所以,我在命令行狀態下,進入這個項目所在的目錄,運行:
                mvn eclipse:eclipse
然後使用Eclipse導入這個項目。如果你是第一次用Eclipse導入用Maven生成的項目,那你需要在Eclipse裏配置一個名叫M2_REPO的Variable,指向你的Maven 2的repository目錄。缺省情況下,它應該位於${user.home}/.m2/repository。
OK!現在我們已經可以在Eclipse中進行工作了。
修改src/main/webapp/WEB-INF/web.xml,加入struts2的FilterDispatcher並設置filter-mapping。在這個示例中我將url-pattern設爲"/app/*",也就是說,url的匹配是基於路徑來做的。這只是我的個人喜好而已,你也可以將它設成"*"。
既然是在講struts2的零配置,當然是可以不要任何配置文件的。但是爲了更好地進行“配置”,我還是建立了struts.xml文件(在src/main/resources目錄下)。我不喜歡url最後都有個action後綴,現在,我在struts.xml中配置struts.action.extension,將這個後綴去掉:
xml 代碼
 
  1. <struts>  
  2.     <constant name="struts.action.extension" value="" />  
  3. </struts>  

然後我在src/test/java下建立demo/RunJetty.java文件,main方法如下:
java 代碼
 
  1. public static void main(String[] args) throws Exception {  
  2.     Server server = new Server(8080); //也可以改成其它端口  
  3.     File rootDir = new File(RunJetty.class.getResource("/").getPath()).getParentFile().getParentFile();  
  4.     String webAppPath = new File(rootDir, "src/main/webapp").getPath();  
  5.     new WebAppContext(server, webAppPath, "/");  
  6.     server.start();  
  7. }  

現在,在Eclipse裏運行或調試這個RunJetty.java,用瀏覽器打開http://localhost:8080/看看吧。如果不出問題,應該可以訪問到webapp目錄下的index.html了。有了Jetty,你還在用MyEclipse或其它插件麼?

二、零配置

首先要澄清一點,這裏說的零配置並不是一點配置都沒有,只是說配置很少而已。
Struts2(我只用過Struts 2.0.6和2.0.9,不清楚其它版本是否支持零配置)引入了零配置的新特性,元數據可以通過規則和註解來表達:A "Zero Configuration" Struts application or plugin uses no additional XML or properties files. Metadata is expressed through convention and annotation.
目前,這個新特性還在測試階段,但經過一段時間的使用,我覺得這個特性已經可用。下面我講一下如何使用它。
1. Actions的定位
以前需要在xml配置文件中配置Action的name和class,如果使用零配置,所帶來的一個問題就是如何定位這些Action。我們需要在web.xml中找到struts2的filter的配置,增加一個名爲actionPackages的init-param,它的值是一個以逗號分隔的Java包名列表,比如:demo.actions1,demo.actions2。struts2將會掃描這些包(包括這些包下邊的子包),在這些包下,所有實現了Action接口的或者是類名以“Action”結尾的類都會被檢查到,並被當做Action。
以前,我們寫Action必須要實現Action接口或者繼承ActionSupport。但是,上面提到的類名以"Action"結尾的類並不需要這樣做,它可以是一個POJO,Struts2支持POJO Action!
下面是actionPackages的配置示例:
xml 代碼
 
  1. <filter>  
  2.   <filter-name>struts2</filter-name>  
  3.   <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>  
  4.   <init-param>  
  5.     <param-name>actionPackages</param-name>  
  6.     <param-value>demo.actions1,demo.actions2</param-value>  
  7.   </init-param>  
  8. </filter>  

2. 示例
現在我們建立demo.actions1.app.person和demo.actions2.app.group兩個包,在demo.actions1.app.person包下建立ListPeopleAction.java,在demo.actions2.app.group下建立ListGroupAction.java。作爲示例,這兩個類只是包含一個execute方法,返回"success"或"error",其它什麼都不做:
java 代碼
 
  1. public String execute() {  
  2.     return "success";  
  3. }  

在Filter的配置中,我指定actionPackages爲demo.actions1,demo.actions2,當系統啓動時,Struts2就會在這兩個包下掃描到demo.actions1.app.person.ListPeopleAction和demo.actions2.app.group.ListGroupAction。

3. Action and Package name
Struts2掃描到Action後,從actionPackages指定的包開始,子包名會成爲這個Action的namespace,而Action的name則由這個Action的類名決定。將類名首字母小寫,如果類名以Action結尾,則去掉"Action"後綴,形成的名字就是這個Action的名字。在如上所述的示例中,actionPackages指定爲demo.actions1,demo.actions2,那麼你可以這樣訪問demo.actions1.app.person.ListPeopleAction:
                http://localhost:8080/app/person/listPeople

4. Results
Struts2是通過"Result"和"Results"兩個類級別的annotations來指定Results的。
作爲示例,我們在webapp目錄下建兩個html文件:success.html和error.html,隨便寫點什麼內容都可以。現在假設我們訪問/app/person/listPeople時,或Action返回success就轉到success.html頁面,若是error就轉到error.html頁面,這只需要在ListPeopleAction類上加上一段註解就可以了:
java 代碼
 
  1. @Results({  
  2.     @Result(name="success", type=NullResult.class, value = "/success.html", params = {}),  
  3.     @Result(name="error", type=NullResult.class, value = "/error.html", params = {})  
  4. })  
  5. public class ListPeopleAction {  
  6.     public String execute() {  
  7.         return "success";  
  8.     }  
  9. }  

同上,我們給ListGroupAction也加上註解。
現在,我們已經完成了一個零配置的示例。我們並沒有在xml文件裏配置ListPeopleAction和ListGroupAction,但它們已經可以工作了!
用Eclipse運行RunJetty,然後用瀏覽器訪問http://localhost:8080/app/person/listPeople和http://localhost:8080/app/group/listGroup看看,是不是正是success.html(或error.html)的內容?

5. Namespaces
如上所述,namespace由包名所形成,但我們可以使用"Namespace"註解來自己指定namespace。

6. Parent Package
這個配置用得較少。Struts2提供一個"ParentPackage"註解來標識Action應該是屬於哪個package。

三、使用COC

如上所述,Struts2用註解來實現零配置。然而,這不是我喜歡的方式。在我看來,這不過是將配置從XML格式換成了註解方式,並不是真的零配置。而且,這種方式也未必比XML形式的配置更好。另外,對元數據的修改必然會導致項目的重新編譯和部署。還有,現在的Struts2版本似乎對Result註解中的params的處理有些問題。
其實,Struts2的actionPackages配置已經使用了COC,那爲什麼不能爲Results也實現COC,從而去除這些每個Action都要寫的註解?
在嚴謹的項目中,package、action的名稱和頁面的路徑、名稱一定存在着某種關係。比如,頁面的路徑可能和package是對應的,頁面的名稱可能和action的名稱是對應的,或是根據某種法則運算得到。我們知道webwork2和struts2有個配置叫global-results。我們爲什麼不能根據這些對應規則寫個Result,將它配到global-results中,從而真正免去result的配置?
事實上,我推薦Struts2的使用者只用Struts2輸出XML或JSON,放棄UI,頁面這層還是使用標準的HTML、CSS和一些JS組件來展現。許多人反映Struts2慢,確實,Struts2是慢,很慢!慢在哪兒?很大一部分因素是UI這層引起的,特別是使用了過多的Struts2的tag,並使用了ajax theme。但是,如果我們放棄了Struts2的笨拙的UI,Result只輸出XML或JSON,UI則使用標準的HTML+CSS,使用JS組件(DOJO、Adobe Spry Framework、YUI-Ext等)來操作Struts2的輸出數據,情況將會如何?我們會得到一個高性能、高可配的、UI和應用服務器的職責分割更爲明確、合理的、更易於靜態化部署的開發組合。
這似乎是閹割了Struts2,但是這樣閹割過的Struts2擺脫了性能低下的包袱,更輕、更現代化。
有些扯遠了,言歸正傳,不管是讓Struts2輸出XML或JSON,還是輸出頁面,我們都有辦法根據項目的規則寫一個Result,將它配到global-results中,從而大大減少Result的配置。
假設我們讓Struts2只輸出JSON,有個jsonplugin可以做這件事。使用JsonResult時,不再需要知道頁面的位置、名稱等信息,它僅僅是數據輸出,那麼我們就可以將這個Result配成全局的,大部分Action將不再需要Result的配置。
作爲示例,我假設我的例子中輸出的兩個html頁面(success.html和error.html)是JSON,我們看看怎麼免去我例子中的兩個Action的Result註解。
首先,我們刪去ListPeopleAction和ListGroupAction兩個Action的註解,並修改struts.xml文件,加入:
xml 代碼
 
  1. <package name="demo-default" extends="struts-default">  
  2. <global-results>  
  3. <result name="success">/success.html</result>  
  4. </global-results>  
  5. </package>  

請記住這只是一個示例,爲了方便,我沒在項目中加入jsonplugin來作真實的演示,我只是假設這個success是json輸出,讀者可以自行使用jsonplugin來作實驗。

現在,離成功不遠了,但是項目仍然不能正常運行。我們的Action返回success,但並不會匹配到global-results中配置。爲什麼呢?因爲,我們這裏是把global-results配置到"demo-default"這個package下的,而Struts2根據actionPackages找到的Action不會匹配到這個package上。解決辦法也很簡單,還記得上面講到的Parent Package吧?給Action加個註解,指定ParentPackage爲"demo-default"。但這樣可不是我喜歡的,其實有更好的辦法,我們在struts.xml中加個constant就好了:
xml 代碼
 
  1. <constant name="struts.configuration.classpath.defaultParentPackage" value="demo-default" />  

現在,大功告成!運行RunJetty來測試下吧!你可以訪問/app/person/listPeople,可以訪問/app/group/listGroup,而所有的配置僅僅是web.xml和struts.xml中的幾行,我們的Java代碼中也沒有加註解。如果再加上幾百個Action呢?配置仍然就這幾行。
可是,某些Action確實需要配置怎麼辦?對這些Action,你可以加註解,也可以針對這些Action來寫些XML配置。一個項目中,大部分Action的配置是可以遵從一定規則的,可以使用規則來簡化配置,只有少部分需要配置,這就是COC。

注:附件demo-struts-annotations.zip是使用註解實現零配置的示例代碼,附件demo-struts-coc.zip是使用global-results後的示例代碼。
另外,我以前寫過一篇文章《改寫Restful2ActionMapper讓Struts2支持REST風格的URL映射》,這裏所說的零配置並不適用於支持REST。也就是說,你要用REST風格的URL映射,你就必須配置。不過還好,使用REST風格後,配置並不複雜。
另外我見到不少人使用Spring來配置和管理Action,其實完全沒有必要!設置struts.objectFactory 等於spring就可以了,如果在Action中有setService1,這個service1在Spring中有配置的話,它會自動注入的。Javaeye論壇中早有相關的討論。

參考:
    http://struts.apache.org/2.x/docs/zero-configuration.html
    http://struts.apache.org/2.x/docs/zero-configuration-scanning.html 
發佈了51 篇原創文章 · 獲贊 0 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章