摘要:介紹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 代碼
- <dependency>
- <groupId>org.apache.struts</groupId>
- <artifactId>struts2-core</artifactId>
- <version>2.0.9</version>
- </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 代碼
- <struts>
- <constant name="struts.action.extension" value="" />
- </struts>
然后我在src/test/java下建立demo/RunJetty.java文件,main方法如下:
java 代碼
- public static void main(String[] args) throws Exception {
- Server server = new Server(8080);
- File rootDir = new File(RunJetty.class.getResource("/").getPath()).getParentFile().getParentFile();
- String webAppPath = new File(rootDir, "src/main/webapp").getPath();
- new WebAppContext(server, webAppPath, "/");
- server.start();
- }
現在,在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 代碼
- <filter>
- <filter-name>struts2</filter-name>
- <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
- <init-param>
- <param-name>actionPackages</param-name>
- <param-value>demo.actions1,demo.actions2</param-value>
- </init-param>
- </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 代碼
- public String execute() {
- return "success";
- }
在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,隨便寫點什么內容都可以?,F在假設我們訪問/app/person/listPeople時,或Action返回success就轉到success.html頁面,若是error就轉到error.html頁面,這只需要在ListPeopleAction類上加上一段注解就可以了:
java 代碼
- @Results({
- @Result(name="success", type=NullResult.class, value = "/success.html", params = {}),
- @Result(name="error", type=NullResult.class, value = "/error.html", params = {})
- })
- public class ListPeopleAction {
- public String execute() {
- return "success";
- }
- }
同上,我們給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 代碼
- <package name="demo-default" extends="struts-default">
- <global-results>
- <result name="success">/success.html</result>
- </global-results>
- </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 代碼
- <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。