Jakarta Struts簡(jiǎn)介
翻譯作者:-
Matrix開(kāi)源技術(shù)-Wingc
翻譯前的話 呵呵,這是wingc第一次真正意義的翻譯技術(shù)文章,自己看E文文檔和把E文翻譯成能讀的中文那真是兩回事啊,翻譯得不好再所難免,您可別罵wingc哦:)若有什么地方不通、不爽或不妥,請(qǐng)見(jiàn)
http://www.onjava.com/pub/a/onjava/2001/09/11/jsp_servlets.html看原文吧。
Web應(yīng)用開(kāi)發(fā)早期曾經(jīng)是那么的“簡(jiǎn)單”,那個(gè)時(shí)候還只是純HTML頁(yè)面和瀏覽器特效而已。由于還涉及不到動(dòng)態(tài)數(shù)據(jù)操作和商業(yè)應(yīng)用,也就省去了很多麻煩。但是這樣的“簡(jiǎn)單”只是過(guò)眼云煙,如今我們不得不為復(fù)雜的基于Web的商業(yè)應(yīng)用開(kāi)發(fā)采用諸多技術(shù)。
本文將介紹如何利用Struts進(jìn)行應(yīng)用開(kāi)發(fā)的前臺(tái)整合的開(kāi)發(fā)過(guò)程。Struts是一個(gè)為開(kāi)發(fā)基于模型(Model)-視圖(View)-控制器(Controller)(MVC)模式的應(yīng)用架構(gòu)的開(kāi)源框架(譯注1),是利用Java Servlet和JSP構(gòu)建Web應(yīng)用的一項(xiàng)非常有用的技術(shù)。
閱讀本文需要讀者具有以下幾方面的開(kāi)發(fā)經(jīng)驗(yàn):JSP、Servlet、自定義標(biāo)簽庫(kù)(custom tag library)和XML。如果讀者想補(bǔ)一補(bǔ)自定義標(biāo)簽庫(kù)的知識(shí),可以參考作者以前關(guān)于這方面的文章。而本文也是關(guān)于介紹如何使用Struts系列文章的上半部分,本系列暫定分為上下兩部分。
新手上路注意事項(xiàng) Struts是一個(gè)基于Sun J2EE平臺(tái)的MVC框架,主要是采用Servlet和JSP技術(shù)來(lái)實(shí)現(xiàn)的。其最初萌芽于Craig McClanahan的構(gòu)思,誕生至今也一年有余了(譯注2)。現(xiàn)在,Struts是Apache軟件基金會(huì)旗下Jakarta項(xiàng)目組的一部分,其官方網(wǎng)站是
http://jakarta.apache.org/struts。由于Struts能充分滿足應(yīng)用開(kāi)發(fā)的需求,簡(jiǎn)單易用,敏捷迅速,在過(guò)去的一年中頗受關(guān)注。Struts把Servlet、JSP、自定義標(biāo)簽和信息資源(message resources)整合到一個(gè)統(tǒng)一的框架中,開(kāi)發(fā)人員利用其進(jìn)行開(kāi)發(fā)時(shí)不用再自己編碼實(shí)現(xiàn)全套MVC模式,極大的節(jié)省了時(shí)間,所以說(shuō)Struts是一個(gè)非常不錯(cuò)的應(yīng)用框架。
目前的Struts 1.0修正版包括完整的文檔,既可以說(shuō)是用戶文檔又是開(kāi)發(fā)指導(dǎo)文檔。如果讀者是JSP新手,或者對(duì)MVC設(shè)計(jì)模式不是太熟的話,可能剛上路時(shí)會(huì)比較慢,不過(guò)不用擔(dān)心,要相信自己會(huì)盡快趕上的:)
此外,應(yīng)該注意到盡管當(dāng)前Struts只是1.0版,但已經(jīng)相當(dāng)穩(wěn)定了,作者從Struts 0.9版就在一個(gè)大規(guī)模的項(xiàng)目中應(yīng)用了(最近升級(jí)到1.0版),至今還沒(méi)有遇到什么麻煩問(wèn)題。實(shí)際上,Struts在這個(gè)要開(kāi)發(fā)復(fù)雜用戶界面的項(xiàng)目中,為我們團(tuán)隊(duì)大大的縮短了開(kāi)發(fā)時(shí)間,在此衷心的感謝Struts項(xiàng)目團(tuán)隊(duì)的所有開(kāi)發(fā)人員。
哦,還有,如果讀者開(kāi)始上路了,要知道Struts的郵件列表可是有相當(dāng)分量的,在這里混混才可保證能及時(shí)跟上Jakarta項(xiàng)目的最新動(dòng)態(tài)哦
http://jakarta.apache.org/site/mail.html。
開(kāi)始上路! Struts框架可分為以下四個(gè)主要部分,其中三個(gè)就和MVC模式緊密相關(guān):
1、模型(Model),本質(zhì)上來(lái)說(shuō)在Struts中Model是一個(gè)Action類(這個(gè)會(huì)在后面詳細(xì)討論),開(kāi)發(fā)者通過(guò)其實(shí)現(xiàn)商業(yè)邏輯,同時(shí)用戶請(qǐng)求通過(guò)控制器(Controller)向Action的轉(zhuǎn)發(fā)過(guò)程是基于由struts-config.xml文件描述的配置信息的。
2、視圖(View),View是由與控制器Servlet配合工作的一整套JSP定制標(biāo)簽庫(kù)構(gòu)成,利用她們我們可以快速建立應(yīng)用系統(tǒng)的界面。
3、控制器(Controller),本質(zhì)上是一個(gè)Servlet,將客戶端請(qǐng)求轉(zhuǎn)發(fā)到相應(yīng)的Action類。
4、一堆用來(lái)做XML文件解析的工具包,Struts是用XML來(lái)描述如何自動(dòng)產(chǎn)生一些JavaBean的屬性的,此外Struts還利用XML來(lái)描述在國(guó)際化應(yīng)用的用戶提示信息的(這樣一來(lái)就了應(yīng)用系統(tǒng)的實(shí)現(xiàn)多語(yǔ)言支持)。
好,下一步咱們來(lái)看看構(gòu)成這個(gè)框架的各個(gè)部分以及相互之間是怎樣運(yùn)作的吧!
搞定配置先 在使用Struts之前,咱們必先設(shè)置好JSP服務(wù)器,以便讓服務(wù)器在用戶請(qǐng)求時(shí),知道該如何將指定后綴的請(qǐng)求轉(zhuǎn)到相應(yīng)的Controller-Struts ActionServlet處理,當(dāng)然,這些配置信息都一般在服務(wù)器啟動(dòng)時(shí)通過(guò)web.xml文件讀入的。我們可以在web.xml定義多個(gè)Controlloer,為每一個(gè)應(yīng)用定義一個(gè)。一個(gè)典型的web.xml文件配置如下,其中有相應(yīng)的注釋,很好懂的,在后面討論Action的時(shí)候,我們將主要分析strutc-config.xml。
<web-app>
<servlet>
<!--
以下配置信息聲明了Struts中的ActionServlet,即一個(gè)名為OreillyAction的Servlet,其具體實(shí)現(xiàn)為org.apache.struts.action.ActionServlet。在這個(gè)配置中還有這個(gè)Servlet的兩個(gè)參數(shù):debug level和detail,此處這兩個(gè)參數(shù)的值都設(shè)為了2,此外還設(shè)置了在啟動(dòng)載入時(shí)創(chuàng)建兩個(gè)實(shí)例。
-->
<servlet-name>OreillyActionServlet</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>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<!--
設(shè)置所有后綴為.action的請(qǐng)求,都轉(zhuǎn)發(fā)到OreillyActionServlet
-->
<servlet-mapping>
<servlet-name> OreillyActionServlet </servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<!--
將初始請(qǐng)求頁(yè)面設(shè)置為login.jsp
-->
<welcome-file-list><welcome-file>login.jsp</welcome-file></welcome-file-list>
<!--
設(shè)置Struts的JSP頁(yè)面要用到的標(biāo)簽庫(kù)和她們的路徑
-->
<taglib>
<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
</web-app>
控制器(Controller) Controller是這個(gè)框架中扮演“交通警察”的角色,當(dāng)客戶端與服務(wù)器有交互動(dòng)作時(shí),都由她來(lái)控制。Controller將HTTP請(qǐng)求封包并轉(zhuǎn)發(fā)到框架中相應(yīng)的對(duì)象,這些對(duì)象可能是一個(gè)JSP頁(yè)面或一個(gè)Action。
Controller在web.xml中設(shè)置為org.apache.struts.action.ActionServlet的一個(gè)實(shí)例,在本例中,這個(gè)實(shí)例就是OreillyActionServlet。在一個(gè)完整的控制過(guò)程中,也就是處理一個(gè)HTTP請(qǐng)求時(shí),在控制過(guò)程之初,這個(gè)Servlet會(huì)從一個(gè)配置文件struts-config.xml中獲取請(qǐng)求與控制動(dòng)作向?qū)?yīng)的配置信息,這個(gè)我們會(huì)在后面詳細(xì)討論,Controller通過(guò)這些配置信息來(lái)決定HTTP請(qǐng)求該往何處轉(zhuǎn)發(fā),而這些Action在接收到轉(zhuǎn)發(fā)來(lái)的請(qǐng)求后,實(shí)現(xiàn)真正的商業(yè)邏輯。我們要注意的非常重要的一點(diǎn)是Action對(duì)象要能夠調(diào)用這個(gè)ActionServlet的若干方法,通過(guò)這個(gè)有力的特性,當(dāng)Action對(duì)象在控制過(guò)程中將請(qǐng)求再向別的Action對(duì)象轉(zhuǎn)發(fā)時(shí)(最初的請(qǐng)求是由ActionServlet獲取,向Action對(duì)象轉(zhuǎn)發(fā),而Action對(duì)象還可以再轉(zhuǎn)發(fā)到別的對(duì)象),我們可以將一些需要共享的數(shù)據(jù)對(duì)象通過(guò)調(diào)用一些方法放入這個(gè)Servlet相關(guān)的一些標(biāo)準(zhǔn)容器中捎帶過(guò)去。
模型(Model) 所謂Model就是在對(duì)用戶請(qǐng)求的整個(gè)控制過(guò)程中,真正處理用戶請(qǐng)求并保存處理結(jié)果的對(duì)象,在整個(gè)過(guò)程中,我們一般利用JavaBean來(lái)把一些信息保存起來(lái)以便在各個(gè)對(duì)象之間傳遞。因?yàn)樵诳蚣苤校琈odel對(duì)象是真正處理商業(yè)邏輯功能的對(duì)象,因此也就是框架中應(yīng)用需求實(shí)現(xiàn)相關(guān)性最大的部分。在Struts的實(shí)現(xiàn)里,Model的具體表現(xiàn)形式就是ActionForm對(duì)象和與其對(duì)應(yīng)的Action對(duì)象了。對(duì)用戶提交表單的數(shù)據(jù)進(jìn)行校驗(yàn),甚至對(duì)數(shù)據(jù)進(jìn)行預(yù)處理都能在ActionForm中完成。通常的應(yīng)用中,一般是一個(gè)Model對(duì)象和一個(gè)請(qǐng)求頁(yè)面對(duì)應(yīng)的關(guān)系,但也可以一個(gè)Model對(duì)象對(duì)應(yīng)多個(gè)頁(yè)面請(qǐng)求。如果struts-config.xml配置文件沒(méi)有指定一個(gè)Model對(duì)象對(duì)應(yīng)的Action,那么控制器將直接把(通過(guò)模型對(duì)象完成數(shù)據(jù)封裝的)請(qǐng)求轉(zhuǎn)到一個(gè)View對(duì)象。
struts-config.xml 前面多次提到的struts-config.xml配置文件是整個(gè)框架的主心骨。web.xml文件定義了一個(gè)請(qǐng)求到來(lái)應(yīng)向何處轉(zhuǎn)發(fā)后,后面的工作就全權(quán)由struts-config.xml管理控制了。可以說(shuō)struts-config.xml就是整個(gè)Struts框架的“扛把子”(譯注3),只有這位“老大”清楚所有請(qǐng)求與動(dòng)作的映射關(guān)系,要是他哪里沒(méi)有搞定或不爽的話,整個(gè)“社團(tuán)”就什么也擺不平了:)如今的應(yīng)用系統(tǒng),XML形式的配置文件越來(lái)越多,如果整個(gè)系統(tǒng)只使用一個(gè)這樣的配置文件的話,那么保持整個(gè)系統(tǒng)的模塊化和可維護(hù)性都非常的輕松。使用配置文件來(lái)描述請(qǐng)求-動(dòng)作的控制過(guò)程和相互關(guān)系,而不是在代碼中將對(duì)象之間的調(diào)用關(guān)系寫死,那么都應(yīng)用系統(tǒng)有變動(dòng)時(shí),我們只用修改配置文件就行了,而不是再重新編譯發(fā)布程序了。
Controller通過(guò)struts-config.xml文件的配置信息確定當(dāng)有請(qǐng)求時(shí)應(yīng)該調(diào)用那個(gè)對(duì)象來(lái)處理,從效率的角度出發(fā),這些信息都是在系統(tǒng)啟動(dòng)時(shí)讀入并存在內(nèi)存中的。下面我們將講解一個(gè)極短小的struts-config.xml文件,文件中定義了一個(gè)與登錄請(qǐng)求對(duì)應(yīng)的登錄動(dòng)作,請(qǐng)求到達(dá)后將被轉(zhuǎn)發(fā)到com.oreilly.ui.authentication.actions.LoginAction這個(gè)Action對(duì)象,該對(duì)象處理的結(jié)果決定向用戶返回的頁(yè)面。這個(gè)例子同時(shí)還示范了一個(gè)Action對(duì)象將請(qǐng)求轉(zhuǎn)發(fā)到別的Action對(duì)象,而例子中另一個(gè)返回的對(duì)象則是一個(gè)View對(duì)象,即我們看到的login.jsp頁(yè)面。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.0//EN"
"
http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd";>
<struts-config>
<!-- ========== Action 映射定義 =================== -->
<action-mappings>
<!-- <action> 屬性的說(shuō)明 -->
<!-
type - 完整的Action實(shí)現(xiàn)類名
name - 該Action要用到的ActionForm名
path - 請(qǐng)求該Action的URI
validate - 如果本屬性為true則在Action動(dòng)作之前其對(duì)應(yīng)的ActionForm的validate方法會(huì)自動(dòng)被調(diào)用,一般用以校驗(yàn)用戶輸入的數(shù)據(jù)
-->
<!-- ~~~~~~~~~~~~~~~~~~~~~ -->
<!-- O'Reilly Main Actions -->
<!-- ~~~~~~~~~~~~~~~~~~~~~ -->
<action path="/Login"
type="com.oreilly.ui.authentication.actions.LoginAction">
<forward name="success" path="/DisplayMainPage.action"/>
<forward name="failure" path="/login.jsp"/>
</action>
</action-mappings>
</struts-config>
視圖(View) View對(duì)象通常來(lái)說(shuō)都是指的JSP頁(yè)面。Struts框架實(shí)際上并沒(méi)有真正的JSP的要求規(guī)范,而是提供了大量的標(biāo)簽庫(kù)來(lái)為開(kāi)發(fā)者更簡(jiǎn)便的將JSP整合到Struts框架中。在Struts中通過(guò)標(biāo)簽定義方式的JSP頁(yè)面能夠?qū)⒂脩敉ㄟ^(guò)表單輸入的數(shù)據(jù)存入一個(gè)JavaBean中,也就是我們前面提到的ActionForm bean。通過(guò)Action類調(diào)用(自動(dòng)或手動(dòng))ActionForm的校驗(yàn)方法來(lái)檢查用戶輸入的數(shù)據(jù),如果發(fā)現(xiàn)不合法的數(shù)據(jù),再通過(guò)Struts的一個(gè)通用機(jī)制將錯(cuò)誤信息返回給用戶顯示。
Struts框架提供了若干個(gè)標(biāo)簽庫(kù),它們有各自不同的用途。由于這些庫(kù)還可以脫離Struts框架單獨(dú)使用,這樣我們也可以在其他系統(tǒng)中嘗試使用這些標(biāo)簽庫(kù),它們包括:
* struts-html - 這個(gè)標(biāo)簽庫(kù)用來(lái)創(chuàng)建動(dòng)態(tài)的HTML頁(yè)面和表單。
* struts-bean - 提供了類似甚至更強(qiáng)于<jsp:useBean>中的功能。
* struts-logic - 用于在頁(yè)面輸出文本信息時(shí)的條件、循環(huán)等流程的控制。
* struts-template - 用于產(chǎn)生有共同風(fēng)格的動(dòng)態(tài)JSP頁(yè)面模板。
此外,可定制標(biāo)簽庫(kù)在Struts中還有一大用處是,通過(guò)資源文件的方式來(lái)實(shí)現(xiàn)應(yīng)用系統(tǒng)的多語(yǔ)言特性,應(yīng)用Struts的系統(tǒng)若想將系統(tǒng)中的用戶交互信息換一種語(yǔ)言的會(huì)很簡(jiǎn)單,更換一個(gè)不同的資源文件就可以了。
大家都開(kāi)始應(yīng)用Struts吧! Struts框架可能對(duì)于大多數(shù)開(kāi)發(fā)人員來(lái)說(shuō),是一門比較新的技術(shù)。但我們現(xiàn)在已經(jīng)可以在不少的應(yīng)用系統(tǒng)中看到Struts的身影了,而我們大可在新的應(yīng)用或正在開(kāi)發(fā)的JSP項(xiàng)目中使用Struts框架。
例如,在作者現(xiàn)在正在為客戶開(kāi)發(fā)的一個(gè)大型數(shù)據(jù)庫(kù)應(yīng)用系統(tǒng)中,商業(yè)邏輯都是通過(guò)EJB來(lái)實(shí)現(xiàn)的,用戶界面則是JSP頁(yè)面。在struts-config.xml文件中定義了用戶輸入表單和對(duì)應(yīng)的Action類,當(dāng)一個(gè)請(qǐng)求發(fā)生時(shí),即用戶數(shù)據(jù)以ActionForm的形式封裝提交到Action時(shí),Action先調(diào)用ActionForm的校驗(yàn)方法,數(shù)據(jù)檢查校驗(yàn)通過(guò)后,Action再調(diào)用相應(yīng)的EJB中的方法來(lái)完成數(shù)據(jù)操作,操作的結(jié)果以XML的形式返回,XML解析后再放入我們數(shù)據(jù)的封裝傳遞JavaBean - ActionForm中顯示到JSP頁(yè)面里返回用戶。
整個(gè)的控制流程(包括Action調(diào)用后的不同的返回結(jié)果)都盡在struts-config.xml中所掌握,這種“中央集權(quán)”的方式非常便于應(yīng)用流程的調(diào)整。而不管是Servlet還是JSP頁(yè)面中(甚至在一些n層的應(yīng)用架構(gòu))都無(wú)需撰寫如何獲取顯示數(shù)據(jù)的代碼。
由于目前作者所開(kāi)發(fā)的是一個(gè)較大型的系統(tǒng),有很多的JSP頁(yè)面和用戶要提交的ActionForm類型,因此發(fā)現(xiàn)Struts的一個(gè)麻煩的地方,那就是:我們要為如此多頁(yè)面和ActionForm開(kāi)發(fā)對(duì)應(yīng)的Action類來(lái)完成控制,因?yàn)槲覀兡壳癑SP和ActionForm與Action是一對(duì)一的關(guān)系。不過(guò)我認(rèn)為如果在項(xiàng)目前期分析和設(shè)計(jì)時(shí)多下些功夫,做出更完美一些的設(shè)計(jì)方案的話,這樣的情況是可以避免的,當(dāng)然,在新產(chǎn)品的開(kāi)發(fā)過(guò)程中,想一步就把所有需求弄清楚明白那也是不可能的。我們不是都有這樣的經(jīng)歷嗎?在開(kāi)發(fā)中的應(yīng)用系統(tǒng)正一步一步走向成熟的時(shí)候,更新和更明確的需求才會(huì)被提出來(lái)。不過(guò),像我們手里這個(gè)利用Struts開(kāi)發(fā)了六個(gè)月的系統(tǒng)也確實(shí)少見(jiàn)了,呵呵。除去這些非技術(shù)因素不談,Struts框架為我們實(shí)現(xiàn)MVC模式節(jié)省了大量的時(shí)間,并且開(kāi)發(fā)出的系統(tǒng)相當(dāng)?shù)姆€(wěn)定,可以說(shuō)是很成熟的產(chǎn)品了。
在本系列文章的第二部分,我們將把各小段代碼集成起來(lái),完成一個(gè)完整的Struts應(yīng)用的實(shí)例,希望大家繼續(xù)和作者一起學(xué)習(xí)Struts!
Sue Spielman是ONJava.com的副編輯,主要擅長(zhǎng)于JSP和Servlet技術(shù),她還是Switchback Software LLC公司的總裁和高級(jí)技術(shù)咨詢專家。
譯注1:雖然常見(jiàn)將Controller、Model和View這三個(gè)翻譯為對(duì)應(yīng)的“控制器”、“模型”和“視圖”,但總感覺(jué)在表達(dá)上還是有欠本意,所以譯文中只在需要名詞定義的地方將三個(gè)中文詞匯寫上,文中其他的發(fā)還是保留E文“真身”。
譯注2:這是2001年9月11日(沒(méi)錯(cuò),就是“911”當(dāng)天哦)發(fā)的文章啦,要知道文章里的“那時(shí)”也是現(xiàn)在的好久之前的時(shí)候了。
譯注3:原文中此處是用“Vito Corleone”來(lái)形容struts-config.xml的,知道唯托.科尼奧尼是誰(shuí)嗎?呵呵,經(jīng)典電影《教父I》和《教父II》里的教父啊,馬龍.白蘭度和羅伯特.德尼羅先后塑造的經(jīng)典形象,所以wingc在這里也用了一點(diǎn)江湖詞匯,請(qǐng)勿見(jiàn)怪。
學(xué)習(xí)Jakarta Struts(第二篇)
本文是三篇學(xué)習(xí)Struts框架系列文章的第二篇(原文請(qǐng)見(jiàn)
http://www.onjava.com/pub/a/onjava/2001/10/31/struts2.html)。在本系列的的第一篇中,我們大致瀏覽了Struts框架,框架的功能以及框架中應(yīng)用到的各個(gè)模塊。而本文,我將利用Struts 1.0為大家演示建立一個(gè)簡(jiǎn)單的應(yīng)用;在第三篇文章中將介紹如何利用Struts的標(biāo)簽在JSP中訪問(wèn)ApplicationResource文件中的信息。
我們?cè)谶@篇文章將會(huì)一步一步的講解Struts的應(yīng)用,以這樣的形式打開(kāi)Struts一道道神秘的大門,通過(guò)這樣的過(guò)程,相信也能激起你在應(yīng)用開(kāi)發(fā)中如何應(yīng)用Struts的靈感。如果你對(duì)Struts的一些術(shù)語(yǔ)不是很清楚的話,可以參考本系列前一篇對(duì)Struts作大體介紹的文章。
再次重復(fù)一遍,本文需要讀者有如下幾方面的知識(shí)和經(jīng)驗(yàn):JSP,Servlets,自定義標(biāo)簽庫(kù)(Custom Tag libraries)和XML。此外,在本文中,我還會(huì)用到Jakarta項(xiàng)目組其他一些好東東,比如Tomcat
http://jakarta.apache.org/tomcat/index.html(實(shí)現(xiàn)Java Servlet和JSP官方標(biāo)準(zhǔn)的Servlet容器,通俗的講就是一個(gè)JSP的Web Server啦)和Ant
http://jakarta.apache.org/ant/index.html(基于Java的自動(dòng)編譯發(fā)布工具,這可是好東東啊)。
作為一名一直使用前沿技術(shù)開(kāi)發(fā)了諸多應(yīng)用的技術(shù)人員,我一直堅(jiān)信掌握新技術(shù),理解該技術(shù)開(kāi)發(fā)的邏輯是至關(guān)重要的。但這往往就是陷住我們學(xué)習(xí)步伐的泥潭,正因如此,我打算將利用Struts開(kāi)發(fā)的一套完整流程作為我們教學(xué)的案例。該流程的這個(gè)案例可謂“麻雀雖小、五臟據(jù)全”,你完全可以將這個(gè)流程應(yīng)用到你手頭那些復(fù)雜龐大的項(xiàng)目中,至少在我們的大項(xiàng)目中應(yīng)用這個(gè)流程效果不錯(cuò)。
有開(kāi)發(fā)復(fù)雜商業(yè)應(yīng)用的開(kāi)發(fā)人員都知道,客戶的需求總是在不停變幻,所以如果有一套規(guī)范的開(kāi)發(fā)流程來(lái)遵循,當(dāng)客戶提出新的需求時(shí),我們至少可以明確哪些“無(wú)理”需求其實(shí)是合理可行的。好,接下里我將在我的這個(gè)例子中向各位展示和應(yīng)用整個(gè)流程。
本文中的示例代碼是StrutsSample應(yīng)用中的一部分,包括build.xml的完整代碼可以到此處
http://www.onjava.com/onjava/2001/10/31/examples/StrutsPartII.jar下載。
Struts開(kāi)發(fā)過(guò)程 從Struts發(fā)布的版本號(hào)可以看出,Struts是個(gè)新玩意,她有好幾個(gè)部分組成,明智的你如果搞清楚了何時(shí)該開(kāi)發(fā)完成合適的部分,那將會(huì)更好的利用我們的開(kāi)發(fā)時(shí)間。從我所開(kāi)發(fā)的幾個(gè)利用Struts應(yīng)用中,我大致總結(jié)出如下這個(gè)比較有效的開(kāi)發(fā)步驟:
1,明確應(yīng)用需求;
2,由用戶輸入和獲取數(shù)據(jù)的角度出發(fā),明確和設(shè)計(jì)出每一個(gè)用戶界面;
3,確定用戶界面的進(jìn)入路徑;
4,由應(yīng)用邏輯信息確定動(dòng)作映射表(ActionMapping);
5,由設(shè)計(jì)完成的用戶界面開(kāi)發(fā)其所用到的類和應(yīng)用函數(shù);
6,由用戶界面中的數(shù)據(jù)信息開(kāi)發(fā)ActionForm和相應(yīng)的數(shù)據(jù)校驗(yàn)方法;
7,ActionMapping中將會(huì)被調(diào)用相應(yīng)的Action或轉(zhuǎn)到相應(yīng)的JSP頁(yè)面,這一步我們先開(kāi)發(fā)這些Action;
8,開(kāi)發(fā)商業(yè)應(yīng)用邏輯,就是相應(yīng)的JavaBean、EJB或其他東東;
9,開(kāi)發(fā)由ActionMapping定義的系統(tǒng)工作流程完成對(duì)應(yīng)的JSP頁(yè)面;
10,完成系統(tǒng)配置文件:struts-config.xml和web.xml;
11,編譯/測(cè)試/發(fā)布。
明確應(yīng)用需求 開(kāi)發(fā)任何應(yīng)用系統(tǒng)的第一步就是收集用戶需求信息。不管一個(gè)用戶邏輯初看上去多么合理,但總有可能在開(kāi)發(fā)時(shí)才發(fā)現(xiàn)它比看上去要難得多。所以,建議擬一份明確的用戶需求列表,這不只是出于開(kāi)發(fā)的目的,還能通過(guò)該表分析用戶需求以確定哪些地方可能需要花更多的精力。
在我們這個(gè)StrutsSample項(xiàng)目中,應(yīng)用需求就是:
作為一個(gè)展示Struts框架應(yīng)用的完整例子,本示例完成的功能是用戶登錄。目的只為明確Struts的應(yīng)用,本示例將不會(huì)涉及到一般復(fù)雜應(yīng)用系統(tǒng)中可能應(yīng)用的安全、數(shù)據(jù)庫(kù)、EJB開(kāi)發(fā)等等相關(guān)技術(shù)。
設(shè)計(jì)用戶界面 這個(gè)應(yīng)用中,包括如下三個(gè)用戶界面:
1)登錄界面,用于用戶名和密碼輸入;
2)當(dāng)?shù)卿浻脩魹楹戏ㄓ脩魰r(shí)的歡迎界面;
3)當(dāng)?shù)卿浭r(shí)的錯(cuò)誤提示界面。
確定用戶界面的進(jìn)入路徑1)登錄界面作為這個(gè)應(yīng)用的默認(rèn)頁(yè)面;
2)歡迎界面只有當(dāng)成功登錄后才能進(jìn)入;
3)任何可能發(fā)生錯(cuò)誤的頁(yè)面能可以進(jìn)入錯(cuò)誤提示界面;
由應(yīng)用邏輯信息確定ActionMapping ActionMapping為整個(gè)應(yīng)用確定的“線路圖”,在配置文件struts-config.xml對(duì)ActionMapping進(jìn)行定義,通過(guò)轉(zhuǎn)發(fā)請(qǐng)求(forward)來(lái)理順應(yīng)用的處理流程,確定應(yīng)用中每個(gè)用戶請(qǐng)求對(duì)應(yīng)的動(dòng)作。
通常我們?cè)陂_(kāi)發(fā)過(guò)程中就逐步確定了ActionMapping所需的信息,開(kāi)發(fā)代碼的過(guò)程就是在由草稿開(kāi)始一步步完善struts-config.xml的過(guò)程。當(dāng)Action類處理完用戶請(qǐng)求后,其返回的的forward就是在ActionMapping中定義的一個(gè)。一個(gè)Action返回的forward完全有多種可能,盡管一個(gè)Action一般只定義其相關(guān)的幾個(gè)forward。那么,如果有多個(gè)Action都可能返回的同一個(gè)forward,那么就可以將其定義為全局轉(zhuǎn)發(fā)(global forward)。這類似于C中的頭文件中全局變量,如果在struts-config.xml描述信息中,某一個(gè)forward并不是在當(dāng)前Action描述中定義的而是全局定義的,那么這個(gè)全局的將起作用,同樣,一個(gè)Action中當(dāng)前定義的forward將覆蓋全局定義。在我們所給的這個(gè)簡(jiǎn)單實(shí)例中,我們定義了全局forward――“error”,當(dāng)某Action返回的forward是“error”這個(gè)映射,那么Errorpage.jsp頁(yè)面將會(huì)顯示給用戶,盡管當(dāng)前Action并沒(méi)有對(duì)其定義。
我們繼續(xù)不斷的開(kāi)發(fā),項(xiàng)目日漸完善,項(xiàng)目相關(guān)的配置文件也會(huì)越來(lái)越詳細(xì)。在下面的例子中,我們將以StrutsSample中用到的struts-confug.xml文件為例,學(xué)習(xí)global forward和一個(gè)Action中相關(guān)映射的定義。下面定義了一個(gè)名為“l(fā)ogin”的Action,其為com.oreilly.actions.LoginAction的實(shí)例,當(dāng)Action處理用戶登錄成功后將一個(gè)名為"success"的forward返回,用戶也就會(huì)看到Welcome.jsp頁(yè)面,如果登錄失敗,Action將返回對(duì)應(yīng)的forward以再顯示Login.jsp給用戶,而如果處理過(guò)程中發(fā)生其他錯(cuò)誤,Action將返回全局定義的forward――“error”,用戶也就會(huì)看到錯(cuò)誤提示頁(yè)面Errorpage.jsp。
<!-- ========== Global Forward 定義 -->
<global-forwards>
<forward name="login" path="/Login.jsp"/>
<forward name="error" path="/Errorpage.jsp"/>
</global-forwards>
<!-- ========== Action Mapping 定義 -->
<action-mappings>
<!-- <action>元素的相關(guān)屬性 -->
<!--
以下只列出常用屬性,其他請(qǐng)參考o(jì)rg.apache.struts.action.ActionMapping的相關(guān)文檔
path - 當(dāng)前Action對(duì)應(yīng)的用戶請(qǐng)求URI路徑
type - 實(shí)現(xiàn)當(dāng)前Action的Java class的完整名字
name - 當(dāng)前Action中用到的ActionForm的名字,其具體信息在配置文件其他地方另有詳細(xì)定義
unknown - 如果將該屬性設(shè)置為true,那么就是聲明這個(gè)Action將處理整個(gè)應(yīng)用中所有未找到相應(yīng)處理Action的請(qǐng)求,當(dāng)然,一個(gè)應(yīng)用系統(tǒng)中也只會(huì)有一個(gè)Action的unknown屬性可以設(shè)為true
scope - Action中所用到的ActionForm的生存期,可以為“request”或“session”,隨著生存期的設(shè)置,該Action也會(huì)在相應(yīng)的時(shí)間被創(chuàng)建
input - 該Action中相關(guān)ActionForm獲取用戶輸入的輸入頁(yè)面,當(dāng)將ActionForm設(shè)為自動(dòng)驗(yàn)證輸入數(shù)據(jù),發(fā)現(xiàn)不合法數(shù)據(jù)返回錯(cuò)誤時(shí),將返回該頁(yè)面
validate - 如果本屬性為true則在Action動(dòng)作之前其對(duì)應(yīng)的ActionForm的validate方法會(huì)自動(dòng)被調(diào)用,一般用以驗(yàn)證用戶輸入的數(shù)據(jù)
forward 元素 - 定義當(dāng)前Action相關(guān)的ActionForward
-->
<!-- =================== -->
<!-- O'Reilly Struts Sample Main Actions -->
<!-- =================== -->
<action path="/login"
type="com.oreilly.actions.LoginAction"
name="loginForm"
scope="request"
input="/Login.jsp">
<forward name="success" path="/Welcome.jsp"/>
<forward name="failure" path="/Login.jsp"/>
</action>
</action-mappings>
在前一篇文章中,我們?cè)f(shuō)過(guò),struts-config.xml就是MVC模式的的Controller。在確定struts-config.xml中的配置信息時(shí),應(yīng)該多花些時(shí)間精力在上面,以保證每一個(gè)Action定義及其相關(guān)定義是符合應(yīng)用的需求的。如果在項(xiàng)目開(kāi)始沒(méi)有詳細(xì)的設(shè)計(jì)其定義,當(dāng)將所有代碼和配置集成到一起的時(shí)候,我們將不可避免的將各部分的代碼和配置完全重新組織一遍。
我們當(dāng)前的例子StrusSample因?yàn)橹皇翘幚碛脩舻卿洠灾恍枰粋€(gè)Action。一個(gè)應(yīng)用系統(tǒng)中所要用到的Action的多少完全依應(yīng)用的大小而定。一旦整套Action的映射完全的定義出來(lái)后,我們就可以一個(gè)一個(gè)開(kāi)發(fā)其具體實(shí)現(xiàn)的Action和ActionForm類,并逐漸將完成的部分一點(diǎn)一點(diǎn)集成起來(lái)。
由設(shè)計(jì)完成的用戶界面開(kāi)發(fā)其所用到的類和應(yīng)用函數(shù) 所有ActionForm的實(shí)現(xiàn)類都是org.apache.struts.ActionForm的子類。一個(gè)ActionForm是與頁(yè)面上的輸入表單相關(guān)聯(lián)的,而且ActionForm的實(shí)現(xiàn)還可以對(duì)用戶輸入數(shù)據(jù)的合法性進(jìn)行驗(yàn)證。作為一個(gè)Java Bean,ActionForm有Set和Get方法,當(dāng)一個(gè)頁(yè)面中表單被提交時(shí),系統(tǒng)將自動(dòng)調(diào)用Set方法將數(shù)據(jù)放入ActionForm中,而Get方法將為在Action中操作這些數(shù)據(jù)所提供。一般來(lái)說(shuō),處理表單中的所有數(shù)據(jù),并進(jìn)行合法性驗(yàn)證都完全可以交由ActionForm來(lái)完成。在應(yīng)用中,就我個(gè)人而言,傾向于將ActionForm和Action劃分到不同的包中,因?yàn)楫?dāng)一個(gè)頁(yè)面中要用到幾對(duì)ActionFrom和Action時(shí),都放在一個(gè)包內(nèi)會(huì)混淆的。下面的代碼,就是實(shí)例中登錄頁(yè)面用到的ActionForm的代碼。
/*
* LoginForm.java
*/
package com.oreilly.forms;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
/**
* 驗(yàn)證用戶要用到的兩個(gè)數(shù)據(jù)
*
* username - 登錄用戶名
* password - 用戶密碼
*
*/
public final class LoginForm extends ActionForm {
private String userName = null;
private String password = null;
/**
* userName的Get方法
* @return String
*/
public String getUserName() {
return (userName);
}
/**
* userName的Set方法
* @param userName
*/
public void setUserName(String newUserName) {
userName = newUserName;
}
/**
* password的Get方法
* @return String
*/
public String getPassword() {
return (password);
}
/**
* password的Set方法
* @param password
*/
public void setPassword(String newPassword) {
password = newPassword;
}
/**
* 重置所有數(shù)據(jù)
*
* @param mapping 當(dāng)前的ActionMapping
* @param request 當(dāng)前Server正在處理的HttpServletRequest
*/
public void reset(ActionMapping mapping, HttpServletRequest request) {
userName = null;
password = null;
}
/**
* 驗(yàn)證當(dāng)前HTTP請(qǐng)求提交上來(lái)的數(shù)據(jù)
* 如果數(shù)據(jù)驗(yàn)證發(fā)現(xiàn)不合法數(shù)據(jù),將返回一個(gè)封裝
* 所有驗(yàn)證錯(cuò)誤的ActionErrors對(duì)象
* 如果數(shù)據(jù)驗(yàn)證通過(guò),該方法返回null或者一個(gè)
* 沒(méi)有封裝任何驗(yàn)證錯(cuò)誤的ActionErrors對(duì)象
*
* @param mapping 當(dāng)前的ActionMapping
* @param request 當(dāng)前Server正在處理的HttpServletRequest
*/
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
// 當(dāng)前ActionForm中,只需要檢查用戶輸入的用戶名數(shù)據(jù)
if( userName == null || userName.length()==0 ){
errors.add("userName",new ActionError("error.userName.required"));
}
return (errors);
}
}
以上的代碼,只有兩點(diǎn)和一般的Java Bean有所不同。其一是reset方法,方法中設(shè)置的值將在表單被reset時(shí)反應(yīng)到其對(duì)應(yīng)的表單項(xiàng)上,即將表單項(xiàng)的數(shù)據(jù)恢復(fù)到默認(rèn)值。其二是validate方法,是用來(lái)驗(yàn)證用戶在表單中所輸入數(shù)據(jù)的方法。在當(dāng)前這個(gè)例子中,我們只驗(yàn)證用戶輸入的用戶名。因?yàn)橐粋€(gè)用戶名其對(duì)應(yīng)的密碼可能為空,所以我們的邏輯就是驗(yàn)證時(shí)不去檢查密碼。驗(yàn)證用戶名,當(dāng)發(fā)現(xiàn)輸入的用戶名為空時(shí),方法就會(huì)產(chǎn)生一個(gè)錯(cuò)誤對(duì)象(ActionError)。
在Struts中用ActionErrors來(lái)裝載多個(gè)錯(cuò)誤,從ActionErrors結(jié)尾的那個(gè)“s”就可以知道她是一個(gè)ActionError對(duì)象的集合。在驗(yàn)證用戶輸入時(shí),可以驗(yàn)證完表單中所有數(shù)據(jù)后,再將可能發(fā)現(xiàn)的多個(gè)錯(cuò)誤通過(guò)ActionErrors返回給用戶,這樣的邏輯應(yīng)該是想當(dāng)然的啦,不可能用戶有五個(gè)不同的輸入錯(cuò)誤,卻要分五次提示,讓用戶修改提交五遍吧,呵呵。
同時(shí),要知道在我們這個(gè)例子中,我們將錯(cuò)誤信息提示給用戶是通過(guò)ApplicationResource.properties文件。這個(gè)文件在Tomcat啟動(dòng)時(shí)通過(guò)web.xml中的定義為這個(gè)應(yīng)用所使用。通常每一個(gè)應(yīng)用都在其WEB-INF目錄下都有web.xml文件來(lái)描述系統(tǒng),而關(guān)于部署應(yīng)用時(shí)具體的結(jié)構(gòu)信息,請(qǐng)參考Tomcat
http://jakarta.apache.org/tomcat/index.html等Server相關(guān)的用戶手冊(cè)。
ApplicationResource.properties文件中可以定義應(yīng)用中所要用到的提示信息的字符串,字符串都通過(guò)一個(gè)鍵值來(lái)唯一確定其位置。在我們這個(gè)例子中,鍵值error.userName.required所對(duì)應(yīng)的字符串信息是“A username is required”,在給用戶顯示錯(cuò)誤信息時(shí),也就通過(guò)鍵值確定的錯(cuò)誤提示顯示該字符串。通過(guò)這樣的機(jī)制,為我們?cè)谙到y(tǒng)中實(shí)現(xiàn)多語(yǔ)言提供了便利,將該文件中的這些字符串翻譯成相應(yīng)的語(yǔ)言,我們的系統(tǒng)就可以實(shí)現(xiàn)西班牙語(yǔ)、德語(yǔ)、法語(yǔ)和漢語(yǔ)等等語(yǔ)言的版本了。
下面這些ApplicationResource.properties中的信息對(duì)于我們這個(gè)簡(jiǎn)單的示例中已經(jīng)夠用了:
login.title=Login Struts Sample
error.userName.required=A username is required
error.login.authenticate=Invalid username/password
errors.footer=</ul><hr>
errors.header=<h3><font color="red">Page
Validation</font></h3>Please correct the
following error(s) before contiuing:<ul>
applicationResources=Cannot load application resources bundle
{0}
頁(yè)面的標(biāo)題,按鈕或其他什么需要文本提示的地方都可以通過(guò)這個(gè)文件來(lái)定義顯示用字符串。我們將在該系列的最后一篇文章,也就是后續(xù)幾個(gè)開(kāi)發(fā)步驟中講解如何通過(guò)Struts的標(biāo)簽從這個(gè)文件中獲取顯示用字符串。
學(xué)習(xí)Jakarta Struts(第三篇)
本文是三篇學(xué)習(xí)Struts框架系列文章的最后一篇(原文請(qǐng)見(jiàn)
http://www.onjava.com/pub/a/onjava/2001/11/14/jsp_servlets.html)。
在第一篇文章《Jakarta Struts簡(jiǎn)介》中,我大致分析了Struts框架,討論了它所能完成的功能,還瀏覽了組成Struts的各個(gè)組成部分。在第二篇文章《學(xué)習(xí)Jakarta Struts》中,我開(kāi)始詳細(xì)描述如何利用Struts來(lái)構(gòu)建一個(gè)簡(jiǎn)單應(yīng)用的過(guò)程步驟。而本篇文章將會(huì)向大家演示如何將ApplicationResource文件中的文本信息,通過(guò)Struts標(biāo)簽在JSP頁(yè)面中顯示出來(lái)。
Action類是連接Struts架構(gòu)和應(yīng)用中業(yè)務(wù)邏輯代碼的橋梁。所以你應(yīng)該盡可能讓Action類小巧簡(jiǎn)單,因?yàn)檎鎸?shí)應(yīng)用中的邏輯處理應(yīng)該是由單獨(dú)分離出來(lái)的邏輯層來(lái)完成的。如果你正在從事n層應(yīng)用的開(kāi)發(fā),你當(dāng)然希望層與層之間的接口越簡(jiǎn)單越好。而事實(shí)上,Action類中的主要方法"perform()"(1.1中為execute())卻有點(diǎn)暗示應(yīng)該在本方法中做點(diǎn)什么的意思。我們知道,每個(gè)Action類都需要從 org.apache.struts.action.Action 繼承而來(lái)。在小型應(yīng)用中,我們的Action類很可能就只要繼承org.apache.struts.action.Action就足夠了;而在某些特定的復(fù)雜應(yīng)用中,我就從我們所實(shí)現(xiàn)的Action類中總結(jié)出來(lái)了一些通用特性。因此,在我看來(lái),構(gòu)造一個(gè)基類將這些通用特性的代碼實(shí)現(xiàn)出來(lái),讓應(yīng)用中所用到的所有Action類不直接繼承org.apache.struts.action.Action,而繼承這個(gè)完成了一些通用特性的基類以實(shí)現(xiàn)代碼重用,是一個(gè)相當(dāng)不錯(cuò)的設(shè)計(jì)。我在StrutsSample中就應(yīng)用了這種方法,構(gòu)造了這樣的一個(gè)基類,該基類的方法在完成復(fù)雜邏輯的和簡(jiǎn)單轉(zhuǎn)發(fā)請(qǐng)求的Action類中都可以使用。
package com.oreilly.actions;
import java.io.IOException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.Enumeration;
import java.util.Properties;
import java.rmi.RemoteException;
import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
這個(gè)類就是使用Struts開(kāi)發(fā)時(shí),所有Action類都要繼承的基類。它把一些通常在實(shí)際應(yīng)用中最有可能被用到的東西都考慮進(jìn)來(lái)了。就這篇文章而言, 類中一些與Struts并不是太緊密相關(guān)的方法將只做注釋而不會(huì)完整的實(shí)現(xiàn),而從事開(kāi)發(fā)工作的你,有興趣的話,請(qǐng)完成這些方法并應(yīng)用這個(gè)類,將為你在實(shí)際項(xiàng)目中的開(kāi)發(fā)快馬加鞭。注意,因?yàn)樗械腁ction類都要從org.apache.struts.action.Action 繼承而來(lái),所以我們的這個(gè)類同樣。
public abstract class AbstStrutsActionBase extends Action {
/**
* 定義一些在struts-config.xml中記錄在案的
* 全局應(yīng)用中皆可可通用的forward標(biāo)識(shí)
*/
protected static final String SUCCESS = "success";
protected static final String FAILURE = "failure";
protected static final String ERROR = "error";
protected static final String LOGIN = "login";
protected static final String CONFIRM = "confirm";
protected Context jndiContext = null;
/**
* 默認(rèn)構(gòu)造方法
*/
public AbstStrutsActionBase() {
}
/**
下面這個(gè)查找EJB實(shí)例的方法將不會(huì)完整實(shí)現(xiàn)。
一般來(lái)說(shuō),Action類應(yīng)該調(diào)用實(shí)現(xiàn)了應(yīng)用的商務(wù)邏輯的EJB會(huì)話bean(或僅僅普通JavaBean)。在大型項(xiàng)目中,開(kāi)發(fā)人員必須劃清層與層之間的界限。在Action類中,我們應(yīng)該拿到獲取含有JNDI信息的環(huán)境的實(shí)例,然后通過(guò)EJB的JNDI名字去查詢獲取它的home接口。過(guò)程并不簡(jiǎn)單,所以下面這個(gè)代碼片斷只是個(gè)給出了必要實(shí)現(xiàn)的小例子。
? 參數(shù)類型String,傳入的要查詢JNDI的名字
? 返回類型Object,即查找到的home接口
? 如果查找失敗,拋出NamingException異常
? 如果獲取資源信息失敗,拋出MissingResourceException異常
*/
public Object lookup(String jndiName)
throws NamingException, MissingResourceException {
// 為調(diào)用EJB對(duì)象,通過(guò)構(gòu)建記錄JNDI信息的Properties對(duì)象
// 來(lái)獲得初始環(huán)境信息
if (jndiContext == null) {
ResourceBundle resource =
ResourceBundle.getBundle("strutssample.properties");
Properties properties = new Properties();
properties.setProperty(
Context.INITIAL_CONTEXT_FACTORY,
resource.getString(Context.INITIAL_CONTEXT_FACTORY));
properties.setProperty(
Context.PROVIDER_URL,
resource.getString(Context.PROVIDER_URL));
properties.setProperty(
Context.SECURITY_PRINCIPAL,
resource.getString(Context.SECURITY_PRINCIPAL));
properties.setProperty(
Context.SECURITY_CREDENTIALS,
resource.getString(Context.SECURITY_CREDENTIALS));
jndiContext = new InitialContext(properties);
}
注意:在真正的產(chǎn)品中,我們應(yīng)該在此處考慮代碼的健壯性,將代碼加入到try/catch塊內(nèi),并記錄所有錯(cuò)誤或重要信息到系統(tǒng)log中。而本例中,我們僅僅把異常往外拋,并假定一定會(huì)找到EJB對(duì)象的home接口并返回。
return (jndiContext.lookup(jndiName));
}
由于Action類將是由Struts來(lái)調(diào)用的。所以它的主要方法應(yīng)該是一個(gè)抽象方法,而由每個(gè)繼承的子類來(lái)具體實(shí)現(xiàn),或者在其中做一些所有Action都會(huì)做的通用機(jī)制,例如記錄log信息。在本例中,我們一切從簡(jiǎn),將其抽象之。
? 參數(shù)mapping:其類型為ActionMapping,將在本Action做跳轉(zhuǎn)選擇用
? 參數(shù)actionForm:由Struts根據(jù)本次HTTP請(qǐng)求數(shù)據(jù)填充完成的ActionForm對(duì)象(可選,如果存在請(qǐng)求數(shù)據(jù)的話)
? 參數(shù)request:此Action所有處理的本次HTTP請(qǐng)求(對(duì)象)
? 參數(shù)response:此Action輸出數(shù)據(jù)所要用到的HTTP響應(yīng)(對(duì)象)
? 如果有I/O錯(cuò)誤出現(xiàn),則本方法拋出IOException異常
? 如果處理時(shí)發(fā)生servlet異常,則本方法拋出ServletException異常
? 本方法處理完請(qǐng)求后按照處理邏輯返回相應(yīng)的頁(yè)面導(dǎo)向(對(duì)象)
public abstract ActionForward perform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException;
}
或者讓這個(gè)抽象方法更有用一點(diǎn),那就在里面干點(diǎn)什么吧,比如像下面這樣在其中記錄log。
{
ActionForward forward = null;
// 只是簡(jiǎn)單的記錄一些提示信息到servlet log
getServlet().log(
"AbstStrutsActionBase.perform() [Action Class: "
+ this.getClass().getName()
+ " ]");
getServlet().log(
"AbstStrutsActionBase.perform() [Form Class : "
+ (form == null ? "null" : form.getClass().getName())
+ " ]");
}
然后,我們?cè)倬帉懙拿總€(gè)Action類都應(yīng)該從AbstStrutsActionBase繼承,并依照處理邏輯編寫各自的perform方法。讓我們用LoginAction為例,看看具體應(yīng)該怎么應(yīng)用吧。
package com.oreilly.actions;
import java.io.IOException;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForward;
import com.oreilly.forms.LoginForm;
/*
LoginAction 將演示一個(gè)Action將如何被Struts架構(gòu)所調(diào)用
在這個(gè)例子中,我們只是簡(jiǎn)單的演示perform方法是如何調(diào)用、執(zhí)行并返回的
*/
public class LoginAction extends AbstStrutsActionBase {
接下來(lái)這個(gè)是驗(yàn)證用戶的方法,本例中沒(méi)有具體實(shí)現(xiàn)。但一個(gè)典型的應(yīng)用方案是調(diào)用JavaBean或者EJB來(lái)完成。用來(lái)查找EJB的lookup方法(在基類中完成的)應(yīng)該在本方法中被調(diào)用,其返回一個(gè)依據(jù)后臺(tái)數(shù)據(jù)庫(kù)驗(yàn)證用戶的接口。
? 參數(shù)類型String,要驗(yàn)證的用戶名
? 參數(shù)類型String,密碼
? 返回類型boolean,如果驗(yàn)證通過(guò)為true,否則為false
public boolean authenticate(String username, String password) {
/* 本方法將先做一個(gè)查找動(dòng)作,獲得驗(yàn)證用戶的EJB對(duì)象的接口并調(diào)用
* 由于本例只演示Action與商務(wù)邏輯層是如何交互的
* 所以具體實(shí)現(xiàn)代碼本例中就不提供了:)
*/
return (true);
}
接下來(lái)我們?cè)贚oginAction中重載基類的perform方法。
? 參數(shù)mapping:其類型為ActionMapping,將在本Action做跳轉(zhuǎn)選擇用
? 參數(shù)actionForm:由Struts根據(jù)本次HTTP請(qǐng)求數(shù)據(jù)填充完成的ActionForm對(duì)象(可選,如果存在請(qǐng)求數(shù)據(jù)的話)
? 參數(shù)request:此Action所有處理的本次HTTP請(qǐng)求(對(duì)象)
? 參數(shù)response:此Action輸出數(shù)據(jù)所要用到的HTTP響應(yīng)(對(duì)象)
? 如果有I/O錯(cuò)誤出現(xiàn),則本方法拋出IOException異常
? 如果處理時(shí)發(fā)生servlet異常,則本方法拋出ServletException異常
? 本方法處理完請(qǐng)求后按照處理邏輯返回相應(yīng)的頁(yè)面導(dǎo)向(對(duì)象)
public ActionForward perform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// 先假定驗(yàn)證失敗,那么要導(dǎo)向的forward當(dāng)然是LOGIN了(見(jiàn)基類定義的全局變量)
boolean validLogin = false;
ActionForward actionForward = mapping.findForward(LOGIN);
// 構(gòu)造出承載ActionError對(duì)象的容器——errors,以備錯(cuò)誤出現(xiàn)時(shí)可用
ActionErrors errors = new ActionErrors();
// 從由本次請(qǐng)求構(gòu)造的ActionForm中提取出所需要的數(shù)據(jù)
LoginForm loginForm = (LoginForm)form;
String userName = null;
String password = null;
if (loginForm != null) {
userName = loginForm.getUserName();
password = loginForm.getPassword();
validLogin = authenticate(userName, password);
}
if (validLogin) {
// 驗(yàn)證成功了,導(dǎo)向到struts-config.xml中定義的SUCCESS
actionForward = mapping.findForward(SUCCESS);
// 存點(diǎn)必要的東東到session,以備后用
request.getSession(true).setAttribute("USERNAME", userName);
} else {
errors.add("login", new ActionError("error.login.authenticate"));
}
// 系統(tǒng)如果用戶界面友好一點(diǎn),我們就應(yīng)該將錯(cuò)誤信息存入request對(duì)象中
// 然后到頁(yè)面,通過(guò)在Struts的標(biāo)簽顯示出來(lái)
if (!errors.empty()) {
saveErrors(request, errors);
}
// 本Action處理完成,導(dǎo)向到合適的forward
return (actionForward);
}
}
注意,這個(gè)LoginAction類就是在struts-config.xml中定義的用來(lái)處理登錄事務(wù)的一個(gè)具體實(shí)現(xiàn)。當(dāng)這個(gè)類被載入并有一個(gè)對(duì)象實(shí)例化后,Struts架構(gòu)就會(huì)調(diào)用它的perform方法。這個(gè)方法是這樣聲明的:
public ActionForward perform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
其中,mapping是一個(gè)記錄與此Action對(duì)應(yīng)的forward導(dǎo)向的對(duì)象,form對(duì)象封裝由客戶端提交的此Action要處理的數(shù)據(jù),還有標(biāo)準(zhǔn)的HttpServletRequest對(duì)象和HttpServletResponse對(duì)象。
有了這些對(duì)象的輔助,此Action就可以拿到需要的東東順利開(kāi)工了。我們的例子中,要處理的數(shù)據(jù)主要是用戶名和密碼,這些都由form對(duì)象提供。實(shí)現(xiàn)驗(yàn)證功能是本應(yīng)用的主要業(yè)務(wù)邏輯,在方法中的具體實(shí)現(xiàn)應(yīng)該是去取EJB的相應(yīng)接口來(lái)操作或者直接去拿數(shù)據(jù)庫(kù)數(shù)據(jù)來(lái)驗(yàn)證。前面那個(gè)AbstStrutsActionBase類中已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的EJB接口查找動(dòng)作,所以如果我們是在開(kāi)發(fā)一個(gè)基于EJB實(shí)現(xiàn)的系統(tǒng),它的可重用性就非常強(qiáng)了。
由驗(yàn)證方法(authenticate())的返回值,Action要接著做出合理的動(dòng)作。如果驗(yàn)證通過(guò),就要讓用戶進(jìn)入正確的頁(yè)面,那么我們就將一些后面可能會(huì)用到的信息存入request對(duì)象(譯注:準(zhǔn)確的講,代碼中是存到了session對(duì)象里,當(dāng)然session對(duì)象是和當(dāng)前request相關(guān)的),并向Struts返回success這個(gè)forward。這個(gè)forward是在struts-config.xml中定義的,然后由ActionMapping封裝起來(lái),在Action處理中可以從中拿出合適的forward做為返回值。如果回頭去看看struts-config.xml中的定義,就會(huì)知道success這個(gè)forward會(huì)將用戶導(dǎo)向至Welcome.jsp這個(gè)頁(yè)面的。如果驗(yàn)證失敗,則將一個(gè)錯(cuò)誤信息存起來(lái),然后導(dǎo)向到一個(gè)錯(cuò)誤提示頁(yè)面顯示出來(lái)。
開(kāi)發(fā)應(yīng)用的業(yè)務(wù)邏輯
在一個(gè)真實(shí)的應(yīng)用系統(tǒng)中,我們應(yīng)該將業(yè)務(wù)邏輯層整合進(jìn)來(lái)了。在我們這個(gè)例子里,我們就應(yīng)該去開(kāi)發(fā)LoginAction中的authenticae方法所調(diào)用到的EJB了。但是正如你所見(jiàn)的,我們完全可以把這一層暫時(shí)屏蔽掉,而利用Struts把前端部分構(gòu)建并能夠讓它跑起來(lái)的。我其實(shí)相當(dāng)推崇的是方法是先將應(yīng)用框架搭建并運(yùn)行起來(lái),然后在開(kāi)發(fā)后臺(tái)實(shí)際的業(yè)務(wù)邏輯層。在應(yīng)用框架完全恰當(dāng)?shù)臉?gòu)建起來(lái)的時(shí)候,后臺(tái)的開(kāi)發(fā)工作所有做的debug工作也少的多了。而且,業(yè)務(wù)邏輯的開(kāi)發(fā)也不是本文所要函概的范圍,所以此處我們略過(guò),不過(guò)我相信你現(xiàn)在一定對(duì)應(yīng)用的全局有了總體的把握了吧!
開(kāi)發(fā)由ActionMapping定義的系統(tǒng)工作流程,完成對(duì)應(yīng)的JSP頁(yè)面
終于可以將所有這些東東整合在一起了。在struts-config.xml配置文件中定義的那些ActionMapping,我們要完成這些ActionMapping定義用到的JSP頁(yè)面。本例中,包括Login.jsp、Welcome.jsp和Errorpage.jsp。還有,盡管我們?cè)诒纠卸际菍ction處理完成forward到JSP頁(yè)面,這在這個(gè)簡(jiǎn)單的例子中是再恰當(dāng)不過(guò)的邏輯流程了,而在實(shí)際利用Struts開(kāi)發(fā)應(yīng)用中呢,當(dāng)然可以從Action forward到其他的Action。我們這個(gè)簡(jiǎn)單的Login.jsp頁(yè)面內(nèi)容是這樣的:
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-form.tld" prefix="form" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:message key="login.title"/></title>
</head>
<body>
<html:errors/>
<h3>Enter your username and password to login:</h3>
<html:form action="login.action" focus="userName" >
<html:text property="userName" size="30" maxlength="30"/>
<html:password property="password" size="16" maxlength="16" redisplay="false"/>
<html:submit property="submit" value="Submit"/>
<html:reset/>
</html:form>
</body>
</html>
Struts在JSP自定義標(biāo)簽庫(kù)的基礎(chǔ)上提供了一套綜合各種功能的標(biāo)簽庫(kù)。利用這些標(biāo)簽庫(kù)很容易的構(gòu)建用戶界面了。使用這些標(biāo)簽庫(kù)的好處之一就是可以利用其提供的很多附加功能。比如在一般的JSP頁(yè)面的表單里我們可以看到這樣常見(jiàn)的HTML片斷:
<input type="text" name="userName" value="">
如果我們使用Struts的標(biāo)簽庫(kù)的話,就可以改成這樣子:
<html:text property ="userName">
不過(guò)我們得現(xiàn)在頁(yè)面中先聲明Struts標(biāo)簽庫(kù)的定義。
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
在這個(gè)例子中,我們會(huì)用到一些Struts標(biāo)簽,但我不準(zhǔn)備在此詳細(xì)講解Struts各種標(biāo)簽庫(kù)的用法。相信在你不斷使用Struts搭建功能復(fù)雜的JSP頁(yè)面的過(guò)程中,你將會(huì)對(duì)所使用過(guò)的標(biāo)簽越來(lái)越熟悉的。到那時(shí),你也將更能體會(huì)到Struts標(biāo)簽的益處,利用它們大大的縮短你的開(kāi)發(fā)時(shí)間。目前,你可以從Struts Developers Guides了解到更多的細(xì)節(jié)。
在我們這個(gè)簡(jiǎn)單例子中,有兩個(gè)重點(diǎn)。其一:
<title><bean:message key="login.title"/></title>
這就是在利用我們前面提到的資源文件ApplicationResource來(lái)在頁(yè)面顯示信息,而不是將信息文本硬編碼到我們的應(yīng)用中。
其二:
<html:errors/>
這就是在頁(yè)面中顯示出ActionErrors的信息,也就是我們?cè)贚oginForm的驗(yàn)證方法和LoginAction中產(chǎn)生的報(bào)錯(cuò)信息的集合對(duì)象。
頁(yè)面中的表單,利用Struts,我們將用如下的標(biāo)簽來(lái)定義:
<html:form action="login.action" focus="userName">
這里的login.action,是和struts-config.xml中定義ActionMapping相匹配的。在頁(yè)面標(biāo)簽中這樣的定義,就將相關(guān)的Action、ActionForm和ActionForward完整的串了起來(lái)。當(dāng)這個(gè)用標(biāo)簽定義的表單提交的時(shí)候,Struts中的ActionServlet就會(huì)將其交由login.action來(lái)處理。具體的過(guò)程我們下面慢慢深入。
在Welcome.jsp中,我們只演示如何將Action中的信息傳遞到頁(yè)面加以利用的一般機(jī)制:
<html>
<title>Welcome to Struts</title>
<body>
<p>Welcome <%= (String)request.getSession().getAttribute("USERNAME") %></p>
</p>You have logged in successfully!</p>
</body>
</html>
還記得嗎?我們?cè)贚oginAction中的perform()方法中將USERNAME放到了session中哦。
完成系統(tǒng)配置文件
我們已經(jīng)就struts-config.xml談了好多了。通常,這個(gè)文件中的信息會(huì)在開(kāi)發(fā)過(guò)程中逐漸完善。但是到了開(kāi)發(fā)過(guò)程的最后一部,我們更應(yīng)該回頭去檢查這個(gè)至關(guān)重要的配置文件,以保證萬(wàn)無(wú)一失:Action、JSP頁(yè)面還有ActionForm都應(yīng)該在文件中正確的定義。此外,我們還不得不說(shuō)到web.xml。這個(gè)文件是JSP容器(例如Tomcat)獲取應(yīng)用相關(guān)配置的重要文件。我們這個(gè)StrutsSample例子所用到的web.xml大致是這樣的:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
This is the web-app configuration that allow the strutsSample to work under
Apache Tomcat.
-->
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"
http://java.sun.com/j2ee/dtds/web-app_2_2.dtd";>
<web-app>
<servlet>
<servlet-name>oreilly</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>application</param-name>
<param-value>com.oreilly.ApplicationResources</param-value>
</init-param>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>validate</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>oreilly</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>Login.jsp</welcome-file>
</welcome-file-list>
<!-- Struts Tag Library Descriptors -->
<taglib>
<taglib-uri>/WEB-INF/struts.tld</taglib-uri>
<taglib-location>/WEB-INF/struts.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-form.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-form.tld</taglib-location>
</taglib>
</web-app>
這里的<servlet>標(biāo)簽定義了org.apache.struts.action.ActionServlet,而且在本例中,我們把這個(gè)定義的servlet叫作“oreilly”,并傳了兩個(gè)初始化參數(shù)給它:其一是我們?yōu)檫@個(gè)應(yīng)用所需的顯示字符串定義的資源文件,其二是指明struts-config.xml文件的位置。相信你也注意到了,在<servlet-mapping>中為這個(gè)Servlet指明的相應(yīng)請(qǐng)求處理串是*.action,這是和我們?cè)陧?yè)面中的表單定義的提交的URL是吻合的。也就是說(shuō),我們通過(guò)<servlet-mapping>標(biāo)簽告訴Tomcat,所有后綴為.action的請(qǐng)求都交給“oreilly”這個(gè)Servlet來(lái)處理。你當(dāng)然可以指定你喜歡的后綴。在Struts附帶的例子中,你可能會(huì)看到通常以.do做為后綴,不過(guò)我認(rèn)為.action更明確一些。<welcome-file-list>標(biāo)簽中定義了本應(yīng)用初始顯示頁(yè)面。最后呢,我們還要把會(huì)用到的Struts標(biāo)簽庫(kù)列在后面。
編譯/測(cè)試/發(fā)布
到此為止,編譯/測(cè)試/發(fā)布應(yīng)用之前的所有工作都完成了。用Ant來(lái)編譯整個(gè)應(yīng)用是相當(dāng)容易的。如果你以前沒(méi)有接觸過(guò)Ant,那最好把這個(gè)研究一下。其實(shí)學(xué)習(xí)和應(yīng)用Ant來(lái)管理一個(gè)應(yīng)用編譯環(huán)境并不難。我把這個(gè)編譯應(yīng)用所要用到的build.xml和例子放到了一起,這篇文章所要用到的所有東東,你都可以點(diǎn)此下載,到時(shí)候你到build.xml所在目錄簡(jiǎn)單執(zhí)行ant命令就可以完成編譯,并打包成strutsSample.war包。當(dāng)然要執(zhí)行ant,你得先去下載Ant。將Ant下載回來(lái)并搭建好環(huán)境可能得花十幾分鐘的時(shí)間哦。
本應(yīng)用的目錄結(jié)構(gòu)如下:
StrutsSample根目錄
*.jsp
WEB-INF目錄
Struts配置文件(struts-config.xml, web.xml)
classes目錄(還是以Java程序文件包結(jié)構(gòu)為路徑)
lib目錄(struts.jar)
拿到了應(yīng)用的war包,我們就將它放到Tomcat的webapps路徑下,然后啟動(dòng)Tomcat。war包會(huì)被自動(dòng)展開(kāi),此應(yīng)用的上下文環(huán)境也會(huì)由Tomcat自動(dòng)建立起來(lái)。我們通過(guò)web.xml告知Tomcat這個(gè)應(yīng)用所需的其他資源在哪里。現(xiàn)在,我們可以通過(guò)
http://localhost:8080/strutsSample來(lái)訪問(wèn)我們的應(yīng)用了。如果沒(méi)有特別指定的話,Tomcat默認(rèn)的端口是8080,我們定義的默認(rèn)初始頁(yè)面Login.jsp也將顯示出來(lái),現(xiàn)在我們來(lái)試試吧。
結(jié)論
通過(guò)本系列的文章,我們利用Struts從應(yīng)用需求開(kāi)始,一步步將整個(gè)應(yīng)用搭建起來(lái)。和普通的JSP技術(shù)相比,通過(guò)Struts開(kāi)發(fā)的應(yīng)用涉及到更多的與之相關(guān)的各類文件,也正是依靠各類文件,我們才可能構(gòu)建一個(gè)適合開(kāi)發(fā)復(fù)雜應(yīng)用的MVC架構(gòu)。我們的第一個(gè)Struts應(yīng)用花了如此多的時(shí)間,是為了要弄清楚Struts的各個(gè)部分到底是如何工作的。
希望本系列Struts文章,能夠幫助你了解Struts是由哪些部分構(gòu)成的,它們能夠完成什么,也希望介紹一個(gè)比較好的開(kāi)發(fā)流程可供你參考。Struts才誕生不久,我有信心它將成為我們構(gòu)建J2EE應(yīng)用的優(yōu)秀工具。