摘要:介紹Struts2中的零配置(Zero Configuration),以及如何用COC來更好地簡化Struts2的配置。在第一章,我使用Maven來創(chuàng)建一個(gè)起點(diǎn)項(xiàng)目;第二章,以該項(xiàng)目為例,講解如何使用Struts2的零配置;第三章,論述第二章中的實(shí)現(xiàn)方式的缺陷,然后講解如何使用COC來改進(jìn)這些缺陷,并進(jìn)一步簡化Struts2的配置。附件是這篇文章用到的示例代碼。
一、從零開始
這里,我將建立一個(gè)新的示例項(xiàng)目,作為講解的起點(diǎn)。我使用JDK 6、Maven 2、Eclipse 3.3來建立這個(gè)示例,如果讀者對Maven2不熟也沒關(guān)系,這只是個(gè)示例。
首先,運(yùn)行下邊的命令:
mvn archetype:create -DgroupId=demo.struts -DartifactId=demo-struts-coc -DarchetypeArtifactId=maven-archetype-webapp
這會(huì)建立如下的目錄結(jié)構(gòu):
|- POM.xml
|- src
|- main
|- resources
|- webapp
|- index.jsp
|- WEB-INF
|- web.xml
然后我們在src/main目錄下新建一個(gè)名為java的目錄,用來放置java代碼。在src下建立test目錄,并在test目錄下建立java目錄,用來放置測試代碼。另外,我這個(gè)示例不想使用JSP,所以我將src/main/webapp目錄下的index.jsp改為index.html。
現(xiàn)在,需要配置該項(xiàng)目要用到哪些lib。在POM.xml中加入struts2-core:
xml 代碼
- <dependency>
- <groupId>org.apache.struts</groupId>
- <artifactId>struts2-core</artifactId>
- <version>2.0.9</version>
- </dependency>
另外,我想在Eclipse里使用jetty來啟動(dòng)項(xiàng)目并進(jìn)行測試,所以在POM.xml中再加入jetty、jetty-util、servlet-api等的依賴,詳情見附件。
我希望使用Eclipse來作為這個(gè)項(xiàng)目的IDE,所以,我在命令行狀態(tài)下,進(jìn)入這個(gè)項(xiàng)目所在的目錄,運(yùn)行:
mvn eclipse:eclipse
然后使用Eclipse導(dǎo)入這個(gè)項(xiàng)目。如果你是第一次用Eclipse導(dǎo)入用Maven生成的項(xiàng)目,那你需要在Eclipse里配置一個(gè)名叫M2_REPO的Variable,指向你的Maven 2的repository目錄。缺省情況下,它應(yīng)該位于${user.home}/.m2/repository。
OK!現(xiàn)在我們已經(jīng)可以在Eclipse中進(jìn)行工作了。
修改src/main/webapp/WEB-INF/web.xml,加入struts2的FilterDispatcher并設(shè)置filter-mapping。在這個(gè)示例中我將url-pattern設(shè)為"/app/*",也就是說,url的匹配是基于路徑來做的。這只是我的個(gè)人喜好而已,你也可以將它設(shè)成"*"。
既然是在講struts2的零配置,當(dāng)然是可以不要任何配置文件的。但是為了更好地進(jìn)行“配置”,我還是建立了struts.xml文件(在src/main/resources目錄下)。我不喜歡url最后都有個(gè)action后綴,現(xiàn)在,我在struts.xml中配置struts.action.extension,將這個(gè)后綴去掉:
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();
- }
現(xiàn)在,在Eclipse里運(yùn)行或調(diào)試這個(gè)RunJetty.java,用瀏覽器打開http://localhost:8080/看看吧。如果不出問題,應(yīng)該可以訪問到webapp目錄下的index.html了。有了Jetty,你還在用MyEclipse或其它插件么?
二、零配置
首先要澄清一點(diǎn),這里說的零配置并不是一點(diǎn)配置都沒有,只是說配置很少而已。
Struts2(我只用過Struts 2.0.6和2.0.9,不清楚其它版本是否支持零配置)引入了零配置的新特性,元數(shù)據(jù)可以通過規(guī)則和注解來表達(dá):A "Zero Configuration" Struts application or plugin uses no additional XML or properties files. Metadata is expressed through convention and annotation.
目前,這個(gè)新特性還在測試階段,但經(jīng)過一段時(shí)間的使用,我覺得這個(gè)特性已經(jīng)可用。下面我講一下如何使用它。
1. Actions的定位
以前需要在xml配置文件中配置Action的name和class,如果使用零配置,所帶來的一個(gè)問題就是如何定位這些Action。我們需要在web.xml中找到struts2的filter的配置,增加一個(gè)名為actionPackages的init-param,它的值是一個(gè)以逗號(hào)分隔的Java包名列表,比如:demo.actions1,demo.actions2。struts2將會(huì)掃描這些包(包括這些包下邊的子包),在這些包下,所有實(shí)現(xiàn)了Action接口的或者是類名以“Action”結(jié)尾的類都會(huì)被檢查到,并被當(dāng)做Action。
以前,我們寫Action必須要實(shí)現(xiàn)Action接口或者繼承ActionSupport。但是,上面提到的類名以"Action"結(jié)尾的類并不需要這樣做,它可以是一個(gè)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. 示例
現(xiàn)在我們建立demo.actions1.app.person和demo.actions2.app.group兩個(gè)包,在demo.actions1.app.person包下建立ListPeopleAction.java,在demo.actions2.app.group下建立ListGroupAction.java。作為示例,這兩個(gè)類只是包含一個(gè)execute方法,返回"success"或"error",其它什么都不做:
java 代碼
- public String execute() {
- return "success";
- }
在Filter的配置中,我指定actionPackages為demo.actions1,demo.actions2,當(dāng)系統(tǒng)啟動(dòng)時(shí),Struts2就會(huì)在這兩個(gè)包下掃描到demo.actions1.app.person.ListPeopleAction和demo.actions2.app.group.ListGroupAction。
3. Action and Package name
Struts2掃描到Action后,從actionPackages指定的包開始,子包名會(huì)成為這個(gè)Action的namespace,而Action的name則由這個(gè)Action的類名決定。將類名首字母小寫,如果類名以Action結(jié)尾,則去掉"Action"后綴,形成的名字就是這個(gè)Action的名字。在如上所述的示例中,actionPackages指定為demo.actions1,demo.actions2,那么你可以這樣訪問demo.actions1.app.person.ListPeopleAction:
http://localhost:8080/app/person/listPeople
4. Results
Struts2是通過"Result"和"Results"兩個(gè)類級(jí)別的annotations來指定Results的。
作為示例,我們在webapp目錄下建兩個(gè)html文件:success.html和error.html,隨便寫點(diǎn)什么內(nèi)容都可以。現(xiàn)在假設(shè)我們訪問/app/person/listPeople時(shí),或Action返回success就轉(zhuǎn)到success.html頁面,若是error就轉(zhuǎn)到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也加上注解。
現(xiàn)在,我們已經(jīng)完成了一個(gè)零配置的示例。我們并沒有在xml文件里配置ListPeopleAction和ListGroupAction,但它們已經(jīng)可以工作了!
用Eclipse運(yùn)行RunJetty,然后用瀏覽器訪問http://localhost:8080/app/person/listPeople和http://localhost:8080/app/group/listGroup看看,是不是正是success.html(或error.html)的內(nèi)容?
5. Namespaces
如上所述,namespace由包名所形成,但我們可以使用"Namespace"注解來自己指定namespace。
6. Parent Package
這個(gè)配置用得較少。Struts2提供一個(gè)"ParentPackage"注解來標(biāo)識(shí)Action應(yīng)該是屬于哪個(gè)package。
三、使用COC
如上所述,Struts2用注解來實(shí)現(xiàn)零配置。然而,這不是我喜歡的方式。在我看來,這不過是將配置從XML格式換成了注解方式,并不是真的零配置。而且,這種方式也未必比XML形式的配置更好。另外,對元數(shù)據(jù)的修改必然會(huì)導(dǎo)致項(xiàng)目的重新編譯和部署。還有,現(xiàn)在的Struts2版本似乎對Result注解中的params的處理有些問題。
其實(shí),Struts2的actionPackages配置已經(jīng)使用了COC,那為什么不能為Results也實(shí)現(xiàn)COC,從而去除這些每個(gè)Action都要寫的注解?
在嚴(yán)謹(jǐn)?shù)捻?xiàng)目中,package、action的名稱和頁面的路徑、名稱一定存在著某種關(guān)系。比如,頁面的路徑可能和package是對應(yīng)的,頁面的名稱可能和action的名稱是對應(yīng)的,或是根據(jù)某種法則運(yùn)算得到。我們知道webwork2和struts2有個(gè)配置叫g(shù)lobal-results。我們?yōu)槭裁床荒芨鶕?jù)這些對應(yīng)規(guī)則寫個(gè)Result,將它配到global-results中,從而真正免去result的配置?
事實(shí)上,我推薦Struts2的使用者只用Struts2輸出XML或JSON,放棄UI,頁面這層還是使用標(biāo)準(zhǔn)的HTML、CSS和一些JS組件來展現(xiàn)。許多人反映Struts2慢,確實(shí),Struts2是慢,很慢!慢在哪兒?很大一部分因素是UI這層引起的,特別是使用了過多的Struts2的tag,并使用了ajax theme。但是,如果我們放棄了Struts2的笨拙的UI,Result只輸出XML或JSON,UI則使用標(biāo)準(zhǔn)的HTML+CSS,使用JS組件(DOJO、Adobe Spry Framework、YUI-Ext等)來操作Struts2的輸出數(shù)據(jù),情況將會(huì)如何?我們會(huì)得到一個(gè)高性能、高可配的、UI和應(yīng)用服務(wù)器的職責(zé)分割更為明確、合理的、更易于靜態(tài)化部署的開發(fā)組合。
這似乎是閹割了Struts2,但是這樣閹割過的Struts2擺脫了性能低下的包袱,更輕、更現(xiàn)代化。
有些扯遠(yuǎn)了,言歸正傳,不管是讓Struts2輸出XML或JSON,還是輸出頁面,我們都有辦法根據(jù)項(xiàng)目的規(guī)則寫一個(gè)Result,將它配到global-results中,從而大大減少Result的配置。
假設(shè)我們讓Struts2只輸出JSON,有個(gè)jsonplugin可以做這件事。使用JsonResult時(shí),不再需要知道頁面的位置、名稱等信息,它僅僅是數(shù)據(jù)輸出,那么我們就可以將這個(gè)Result配成全局的,大部分Action將不再需要Result的配置。
作為示例,我假設(shè)我的例子中輸出的兩個(gè)html頁面(success.html和error.html)是JSON,我們看看怎么免去我例子中的兩個(gè)Action的Result注解。
首先,我們刪去ListPeopleAction和ListGroupAction兩個(gè)Action的注解,并修改struts.xml文件,加入:
xml 代碼
- <package name="demo-default" extends="struts-default">
- <global-results>
- <result name="success">/success.html</result>
- </global-results>
- </package>
請記住這只是一個(gè)示例,為了方便,我沒在項(xiàng)目中加入jsonplugin來作真實(shí)的演示,我只是假設(shè)這個(gè)success是json輸出,讀者可以自行使用jsonplugin來作實(shí)驗(yàn)。
現(xiàn)在,離成功不遠(yuǎn)了,但是項(xiàng)目仍然不能正常運(yùn)行。我們的Action返回success,但并不會(huì)匹配到global-results中配置。為什么呢?因?yàn)椋覀冞@里是把global-results配置到"demo-default"這個(gè)package下的,而Struts2根據(jù)actionPackages找到的Action不會(huì)匹配到這個(gè)package上。解決辦法也很簡單,還記得上面講到的Parent Package吧?給Action加個(gè)注解,指定ParentPackage為"demo-default"。但這樣可不是我喜歡的,其實(shí)有更好的辦法,我們在struts.xml中加個(gè)constant就好了:
xml 代碼
- <constant name="struts.configuration.classpath.defaultParentPackage" value="demo-default" />
現(xiàn)在,大功告成!運(yùn)行RunJetty來測試下吧!你可以訪問/app/person/listPeople,可以訪問/app/group/listGroup,而所有的配置僅僅是web.xml和struts.xml中的幾行,我們的Java代碼中也沒有加注解。如果再加上幾百個(gè)Action呢?配置仍然就這幾行。
可是,某些Action確實(shí)需要配置怎么辦?對這些Action,你可以加注解,也可以針對這些Action來寫些XML配置。一個(gè)項(xiàng)目中,大部分Action的配置是可以遵從一定規(guī)則的,可以使用規(guī)則來簡化配置,只有少部分需要配置,這就是COC。