Sung in Blog

                     一些技術(shù)文章 & 一些生活雜碎

          (第1部分)

          一、 什么是Struts

          框架(Framework)是可重用的,半完成的應(yīng)用程序,可以用來(lái)產(chǎn)生專門的定制程序。

          您只要細(xì)心地研究真實(shí)的應(yīng)用程序,就會(huì)發(fā)現(xiàn)程序大致上由兩類性質(zhì)不同的組件組成,一類與程序要處理的具體事務(wù)密切相關(guān),我們不妨把它們叫做業(yè)務(wù)組件;另一類是應(yīng)用服務(wù)。比如說:一個(gè)稅務(wù)征管系統(tǒng)和一個(gè)圖書管理系統(tǒng)會(huì)在處理它們的業(yè)務(wù)方面存在很大的差異,這些直接處理業(yè)務(wù)的組件由于業(yè)務(wù)性質(zhì)的不同不大可能在不同的系統(tǒng)中重用,而另一些組件如決定程序流向的控制、輸入的校驗(yàn)、錯(cuò)誤處理及標(biāo)簽庫(kù)等這些只與程序相關(guān)的組件在不同的系統(tǒng)中可以很好地得到重用。人們自然會(huì)想要是把這些在不同應(yīng)用程序中有共性的一些東西抽取出來(lái),做成一個(gè)半成品程序,這樣的半成品就是所謂的程序框架,再做一個(gè)新的東西時(shí)就不必白手起家,而是可以在這個(gè)基礎(chǔ)上開始搭建。實(shí)際上,有些大型軟件企業(yè)選擇自己搭建這樣的框架。但大多數(shù)中小型軟件企業(yè)或者其他組織,沒有條件自己建立框架。

          Struts作為一個(gè)開放原代碼的應(yīng)用框架,在最近幾年得到了飛速的發(fā)展,在JSP Web應(yīng)用開發(fā)中應(yīng)用得非常廣泛,有的文獻(xiàn)上說它已經(jīng)成為JSP Web應(yīng)用框架的事實(shí)上的標(biāo)準(zhǔn)。那么,究竟什么是Struts呢?

          要回答這個(gè)問題還得從JSP Web應(yīng)用的兩種基本的結(jié)構(gòu)模式:Model 1和Model 2說起,為了給讀者一些實(shí)實(shí)在在的幫助,并力圖讓學(xué)習(xí)曲線變得平坦一些,我想采用實(shí)例驅(qū)動(dòng)的方法來(lái)逐步深入地回答有關(guān)問題,因?yàn)?,學(xué)一門技術(shù)的最好方法莫過于在實(shí)踐中學(xué)習(xí)、在實(shí)踐中體會(huì),逐步加深對(duì)其精神實(shí)質(zhì)的理解和把握,而不是一上來(lái)就引入一大堆新概念讓大家覺得無(wú)所適從,或者死記硬背一大堆概念而面對(duì)一個(gè)真正的實(shí)際需求束手無(wú)策。正如,一個(gè)人即使在書本上學(xué)成了游泳博士,只要他不下水,我想他也是不大可能真正會(huì)游泳的。

          Model 1結(jié)構(gòu)如圖1所示:



          圖1

          mode1 1是一個(gè)以JSP文件為中心的模式,在這種模式中JSP頁(yè)面不僅負(fù)責(zé)表現(xiàn)邏輯,也負(fù)責(zé)控制邏輯。專業(yè)書籍上稱之為邏輯耦合在頁(yè)面中,這種處理方式,對(duì)一些規(guī)模很小的項(xiàng)目如:一個(gè)簡(jiǎn)單的留言簿,也沒什么太大的壞處,實(shí)際上,人們開始接觸一些對(duì)自己來(lái)說是新的東西的時(shí)候,比如,用JSP訪問數(shù)據(jù)庫(kù)時(shí),往往喜歡別人能提供一個(gè)包含這一切的單個(gè)JSP頁(yè)面,因?yàn)檫@樣在一個(gè)頁(yè)面上他就可以把握全局,便于理解。但是,用Model 1模式開發(fā)大型時(shí),程序流向由一些互相能夠感知的頁(yè)面決定,當(dāng)頁(yè)面很多時(shí)要清楚地把握其流向?qū)⑹呛軓?fù)雜的事情,當(dāng)您修改一頁(yè)時(shí)可能會(huì)影響相關(guān)的很多頁(yè)面,大有牽一發(fā)而動(dòng)全身的感覺,使得程序的修改與維護(hù)變得異常困難;還有一個(gè)問題就是程序邏輯開發(fā)與頁(yè)面設(shè)計(jì)糾纏在一起,既不便于分工合作也不利于代碼的重用,這樣的程序其健壯性和可伸縮性都不好。

          Grady Booch等人在UML用戶指南一書中,強(qiáng)調(diào)建模的重要性時(shí),打了一個(gè)制作狗窩、私人住宅、和大廈的形象比喻來(lái)說明人們處理不同規(guī)模的事物時(shí)應(yīng)該采用的合理方法一樣,人們對(duì)不同規(guī)模的應(yīng)用程序也應(yīng)該采用不同的模式。

          為了克服Model 1的缺陷,人們引入了Model 2,如圖2所示:



          圖2

          它引入了"控制器"這個(gè)概念,控制器一般由servlet來(lái)?yè)?dān)任,客戶端的請(qǐng)求不再直接送給一個(gè)處理業(yè)務(wù)邏輯的JSP頁(yè)面,而是送給這個(gè)控制器,再由控制器根據(jù)具體的請(qǐng)求調(diào)用不同的事務(wù)邏輯,并將處理結(jié)果返回到合適的頁(yè)面。因此,這個(gè)servlet控制器為應(yīng)用程序提供了一個(gè)進(jìn)行前-后端處理的中樞。一方面為輸入數(shù)據(jù)的驗(yàn)證、身份認(rèn)證、日志及實(shí)現(xiàn)國(guó)際化編程提供了一個(gè)合適的切入點(diǎn);另一方面也提供了將業(yè)務(wù)邏輯從JSP文件剝離的可能。業(yè)務(wù)邏輯從JSP頁(yè)面分離后,JSP文件蛻變成一個(gè)單純完成顯示任務(wù)的東西,這就是常說的View。而獨(dú)立出來(lái)的事務(wù)邏輯變成人們常說的Model,再加上控制器Control本身,就構(gòu)成了MVC模式。實(shí)踐證明,MVC模式為大型程序的開發(fā)及維護(hù)提供了巨大的便利。

          其實(shí),MVC開始并不是為Web應(yīng)用程序提出的模式,傳統(tǒng)的MVC要求M將其狀態(tài)變化通報(bào)給V,但由于Web瀏覽器工作在典型的拉模式而非推模式,很難做到這一點(diǎn)。因此有些人又將用于Web應(yīng)用的MVC稱之為MVC2。正如上面所提到的MVC是一種模式,當(dāng)然可以有各種不同的具體實(shí)現(xiàn),包括您自己就可以實(shí)現(xiàn)一個(gè)體現(xiàn)MVC思想的程序框架,Struts就是一種具體實(shí)現(xiàn)MVC2的程序框架。它的大致結(jié)構(gòu)如圖三所示:



          圖三

          圖三基本勾勒出了一個(gè)基于Struts的應(yīng)用程序的結(jié)構(gòu),從左到右,分別是其表示層(view)、控制層(controller)、和模型層(Model)。其表示層使用Struts標(biāo)簽庫(kù)構(gòu)建。來(lái)自客戶的所有需要通過框架的請(qǐng)求統(tǒng)一由叫ActionServlet的servlet接收(ActionServlet Struts已經(jīng)為我們寫好了,只要您應(yīng)用沒有什么特別的要求,它基本上都能滿足您的要求),根據(jù)接收的請(qǐng)求參數(shù)和Struts配置(struts-config.xml)中ActionMapping,將請(qǐng)求送給合適的Action去處理,解決由誰(shuí)做的問題,它們共同構(gòu)成Struts的控制器。Action則是Struts應(yīng)用中真正干活的組件,開發(fā)人員一般都要在這里耗費(fèi)大量的時(shí)間,它解決的是做什么的問題,它通過調(diào)用需要的業(yè)務(wù)組件(模型)來(lái)完成應(yīng)用的業(yè)務(wù),業(yè)務(wù)組件解決的是如何做的問題,并將執(zhí)行的結(jié)果返回一個(gè)代表所需的描繪響應(yīng)的JSP(或Action)的ActionForward對(duì)象給ActionServlet以將響應(yīng)呈現(xiàn)給客戶。

          過程如圖四所示:



          圖四

          這里要特別說明一下的是:就是Action這個(gè)類,上面已經(jīng)說到了它是Struts中真正干活的地方,也是值得我們高度關(guān)注的地方。可是,關(guān)于它到底是屬于控制層還是屬于模型層,存在兩種不同的意見,一種認(rèn)為它屬于模型層,如:《JSP Web編程指南》;另一些則認(rèn)為它屬于控制層如:《Programming Jakarta Struts》、《Mastering Jakarta Struts》和《Struts Kick Start》等認(rèn)為它是控制器的一部分,還有其他一些書如《Struts in Action》也建議要避免將業(yè)務(wù)邏輯放在Action類中,也就是說,圖3中Action后的括號(hào)中的內(nèi)容應(yīng)該從中移出,但實(shí)際中確有一些系統(tǒng)將比較簡(jiǎn)單的且不打算重用的業(yè)務(wù)邏輯放在Action中,所以在圖中還是這樣表示。顯然,將業(yè)務(wù)對(duì)象從Action分離出來(lái)后有利于它的重用,同時(shí)也增強(qiáng)了應(yīng)用程序的健壯性和設(shè)計(jì)的靈活性。因此,它實(shí)際上可以看作是Controller與Model的適配器,如果硬要把它歸于那一部分,筆者更傾向于后一種看法,即它是Controller的一部分,換句話說,它不應(yīng)該包含過多的業(yè)務(wù)邏輯,而應(yīng)該只是簡(jiǎn)單地收集業(yè)務(wù)方法所需要的數(shù)據(jù)并傳遞給業(yè)務(wù)對(duì)象。實(shí)際上,它的主要職責(zé)是:

        1. 校驗(yàn)前提條件或者聲明
        2. 調(diào)用需要的業(yè)務(wù)邏輯方法
        3. 檢測(cè)或處理其他錯(cuò)誤
        4. 路由控制到相關(guān)視圖

          上面這樣簡(jiǎn)單的描述,初學(xué)者可能會(huì)感到有些難以接受,下面舉個(gè)比較具體的例子來(lái)進(jìn)一步幫助我們理解。如:假設(shè),我們做的是個(gè)電子商務(wù)程序,現(xiàn)在程序要完成的操作任務(wù)是提交定單并返回定單號(hào)給客戶,這就是關(guān)于做什么的問題,應(yīng)該由Action類完成,但具體怎么獲得數(shù)據(jù)庫(kù)連接,插入定單數(shù)據(jù)到數(shù)據(jù)庫(kù)表中,又怎么從數(shù)據(jù)庫(kù)表中取得這個(gè)定單號(hào)(一般是自增數(shù)據(jù)列的數(shù)據(jù)),這一系列復(fù)雜的問題,這都是解決怎么做的問題,則應(yīng)該由一個(gè)(假設(shè)名為orderBo)業(yè)務(wù)對(duì)象即Model來(lái)完成。orderBo可能用一個(gè)返回整型值的名為submitOrder的方法來(lái)做這件事,Action則是先校驗(yàn)定單數(shù)據(jù)是否正確,以免常說的垃圾進(jìn)垃圾出;如果正確則簡(jiǎn)單地調(diào)用orderBo的submitOrder方法來(lái)得到定單號(hào);它還要處理在調(diào)用過程中可能出現(xiàn)任何錯(cuò)誤;最后根據(jù)不同的情況返回不同的結(jié)果給客戶。

          二、為什么要使用Struts框架

          既然本文的開始就說了,自己可以建這種框架,為什么要使用Struts呢?我想下面列舉的這些理由是顯而易見的:首先,它是建立在MVC這種公認(rèn)的好的模式上的,Struts在M、V和C上都有涉及,但它主要是提供一個(gè)好的控制器和一套定制的標(biāo)簽庫(kù)上,也就是說它的著力點(diǎn)在C和V上,因此,它天生就有MVC所帶來(lái)的一系列優(yōu)點(diǎn),如:結(jié)構(gòu)層次分明,高可重用性,增加了程序的健壯性和可伸縮性,便于開發(fā)與設(shè)計(jì)分工,提供集中統(tǒng)一的權(quán)限控制、校驗(yàn)、國(guó)際化、日志等等;其次,它是個(gè)開源項(xiàng)目得到了包括它的發(fā)明者Craig R.McClanahan在內(nèi)的一些程序大師和高手持續(xù)而細(xì)心的呵護(hù),并且經(jīng)受了實(shí)戰(zhàn)的檢驗(yàn),使其功能越來(lái)越強(qiáng)大,體系也日臻完善;最后,是它對(duì)其他技術(shù)和框架顯示出很好的融合性。如,現(xiàn)在,它已經(jīng)與tiles融為一體,可以展望,它很快就會(huì)與JSF等融會(huì)在一起。當(dāng)然,和其他任何技術(shù)一樣,它也不是十全十美的,如:它對(duì)類和一些屬性、參數(shù)的命名顯得有些隨意,給使用帶來(lái)一些不便;還有如Action類execute方法的只能接收一個(gè)ActionForm參數(shù)等。但瑕不掩瑜,這些沒有影響它被廣泛使用。

          三、Struts的安裝與基本配置

          我們主要針對(duì)Struts1.1版本進(jìn)行講解,這里假定讀者已經(jīng)配置好java運(yùn)行環(huán)境和相應(yīng)的Web容器,本文例子所使用的是j2sdk和Tomcat4.1.27。下面,將采用類似于step by step的方式介紹其基礎(chǔ)部分。

          安裝Struts
          到http://jakarta.apache.org/ 下載Struts的安裝文件,本文例子使用的是1.1版。

          接下來(lái)您要進(jìn)行如下幾個(gè)步驟來(lái)完成安裝:
          1、解壓下載的安裝文件到您的本地硬盤
          2、生成一個(gè)新的Web應(yīng)用,假設(shè)我們生成的應(yīng)用程序的根目錄在/Webapps/mystruts目錄。在server.xml文件中為該應(yīng)用新建一個(gè)別名如/mystruts
          3、從第1步解壓的文件中拷貝下列jar文件到/Webapps/mystruts/WEB-INF/lib目錄,主要文件有如下一些。

          struts.jar
          commons-beanutils.jar
          commons-collections.jar
          commons-dbcp.jar
          commons-digester.jar
          commons-logging.jar
          commons-pool.jar
          commons-services.jar
          commons-validator.jar


          4、創(chuàng)建一個(gè)web.xml文件,這是一個(gè)基于servlet的Web應(yīng)用程序都需要的部署描述文件,一個(gè)Struts Web應(yīng)用,在本質(zhì)上也是一個(gè)基于servlet的Web應(yīng)用,它也不能例外。

          Struts有兩個(gè)組件要在該文件中進(jìn)行配置,它們是:ActionServlet和標(biāo)簽庫(kù)。下面是一個(gè)配置清單:

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3
          //EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
          <web-app>
            <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>
              <init-param>
                <param-name>debug</param-name>
                <param-value>2</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>
            <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>


          上面我們?cè)趙eb.xml中完成了對(duì)servlet和標(biāo)簽庫(kù)的基本配置,而更多的框架組件要在struts-config.xml中進(jìn)行配置:

          5、創(chuàng)建一個(gè)基本的struts-config.xml文件,并把它放在/Webapps/mystruts/WEB-INF/目錄中,該文件是基于Struts應(yīng)用程序的配置描述文件,它將MVC結(jié)構(gòu)中的各組件結(jié)合在一起,開發(fā)的過程中會(huì)不斷對(duì)它進(jìn)行充實(shí)和更改。在Struts1.0時(shí),一個(gè)應(yīng)用只能有一個(gè)這樣的文件,給分工開發(fā)帶來(lái)了一些不便,在Struts1.1時(shí),可以有多個(gè)這樣的文件,將上述缺點(diǎn)克服了。需在該文件中配置的組件有:data-sources

          global-execptions
          form-beans
          global-forwards
          action-mappings
          controller
          message-resources
          plug-in


          配置清單如下:

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1
          //EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
          <struts-config>
            <message-resources parameter="ApplicationResources" />
          </struts-config>


          到此為止,我們已經(jīng)具備了完成一個(gè)最簡(jiǎn)單Struts應(yīng)用的所需的各種組件。前面已經(jīng)提到,在開發(fā)過程中我們會(huì)不斷充實(shí)和修改上面兩個(gè)配置描述文件。下面我們將實(shí)際做一個(gè)非常簡(jiǎn)單的應(yīng)用程序來(lái)體驗(yàn)一下Struts應(yīng)用開發(fā)的真實(shí)過程,以期對(duì)其有一個(gè)真實(shí)的認(rèn)識(shí)。在完成基礎(chǔ)部分的介紹后,筆者會(huì)給出一些在實(shí)際開發(fā)中經(jīng)常用到而又讓初學(xué)者感到有些難度的實(shí)例。最后,會(huì)介紹Struts與其他框架的關(guān)系及結(jié)合它們生成應(yīng)用程序的例子.

          下面,我們就從一個(gè)最簡(jiǎn)單的登錄例子入手,以對(duì)Struts的主要部分有一些直觀而清晰的認(rèn)識(shí)。這個(gè)例子功能非常簡(jiǎn)單,假設(shè)有一個(gè)名為lhb的用戶,其密碼是awave,程序要完成的任務(wù)是,呈現(xiàn)一個(gè)登錄界面給用戶,如果用戶輸入的名稱和密碼都正確返回一個(gè)歡迎頁(yè)面給用戶,否則,就返回登錄頁(yè)面要求用戶重新登錄并顯示相應(yīng)的出錯(cuò)信息。這個(gè)例子在我們講述Struts的基礎(chǔ)部分時(shí)會(huì)反復(fù)用到。之所以選用這個(gè)簡(jiǎn)單的程序作為例子是因?yàn)椴幌胱屵^于復(fù)雜的業(yè)務(wù)邏輯來(lái)沖淡我們的主題。

          因?yàn)镾truts是建立在MVC設(shè)計(jì)模式上的框架,你可以遵從標(biāo)準(zhǔn)的開發(fā)步驟來(lái)開發(fā)你的Struts Web應(yīng)用程序,這些步驟大致可以描述如下:
          1定義并生成所有代表應(yīng)用程序的用戶接口的Views,同時(shí)生成這些Views所用到的所有ActionForms并將它們添加到struts-config.xml文件中。
          2在ApplicationResource.properties文件中添加必要的MessageResources項(xiàng)目
          3生成應(yīng)用程序的控制器。
          4在struts-config.xml文件中定義Views與 Controller的關(guān)系。
          5生成應(yīng)用程序所需要的model組件
          6編譯、運(yùn)行你的應(yīng)用程序.

          (第2部分)


          下面,我們就一步步按照上面所說的步驟來(lái)完成我們的應(yīng)用程序:

          第一步,我們的應(yīng)用程序的Views部分包含兩個(gè).jsp頁(yè)面:一個(gè)是登錄頁(yè)面logon.jsp,另一個(gè)是用戶登錄成功后的用戶功能頁(yè)main.jsp,暫時(shí)這個(gè)頁(yè)面只是個(gè)簡(jiǎn)單的歡迎頁(yè)面。

          其中,logon.jsp的代碼清單如下:

          <%@ page contentType="text/html; charset=UTF-8" %>
          <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
          <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
          <HTML>
          <HEAD>
          <TITLE><bean:message key="logon.jsp.title"/></TITLE>
          <html:base/>
          </HEAD>
          <BODY>
          <h3><bean:message key="logon.jsp.page.heading"/></h3>
          <html:errors/>
          <html:form action="/logonAction.do" focus="username">
          <TABLE border="0" width="100%">
          <TR>
          <TH align="right"><bean:message key="logon.jsp.prompt.username"/></TH>
          <TD align="left"><html:text property="username"/></TD>
          </TR>
          <TR>
          <TH align="right"><bean:message key="logon.jsp.prompt.password"/></TH>
          <TD align="left"><html:password property="password"/></TD>
          </TR>
          <TR>
          <TD align="right">
            <html:submit><bean:message key="logon.jsp.prompt.submit"/></html:submit>
          </TD>
          <TD align="left">
            <html:reset><bean:message key="logon.jsp.prompt.reset"/></html:reset>
          </TD>
          </TR>
          </TABLE>
          </html:form>
          </BODY>
          </HTML>



          main.jsp的代碼清單如下:

          <%@ page contentType="text/html; charset=UTF-8" %>
          <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
          <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
          
          <HTML>
          <HEAD>
          <TITLE><bean:message key="main.jsp.title"/></TITLE>
          <html:base/>
          </HEAD>
          <BODY>
          <logic:present name="userInfoForm">
          <H3>
            <bean:message key="main.jsp.welcome"/> 
            <bean:write name="userInfoForm" property="username"/>!
          </H3>
          </logic:present>
          </BODY>
          </HTML>



          首先,我們看一下logon.jsp文件,會(huì)發(fā)現(xiàn)它有這么兩個(gè)鮮明的特點(diǎn):一是文件頭部有諸如:
          <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
          <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

          這樣的指令代碼,他們的作用就是指示頁(yè)面要用到struts的自定義標(biāo)簽,標(biāo)簽庫(kù)uri是一個(gè)邏輯引用,標(biāo)簽庫(kù)的描述符(tld)的位置在web.xml文件中給出,見上篇文章的配置部分。struts的標(biāo)簽庫(kù)主要由四組標(biāo)簽組成,它們分別是:

        5. bean標(biāo)簽,作用是在jsp中操縱bean
        6. logic標(biāo)簽,作用是在jsp中進(jìn)行流程控制
        7. html標(biāo)簽,作用是顯示表單等組件
        8. template標(biāo)簽,作用是生成動(dòng)態(tài)模板

          關(guān)于每類標(biāo)簽的具體作用及語(yǔ)法,因受篇幅限制,不在這里詳細(xì)討論,大家可參考struts手冊(cè)之類的資料。只是心里要明白所謂標(biāo)簽其后面的東西就是一些類,這點(diǎn)與bean有些相似,它們?cè)诤蠖诉\(yùn)行,生成標(biāo)準(zhǔn)的html標(biāo)簽返回給瀏覽器。

          要使用它們顯然要把它們的標(biāo)簽庫(kù)描述文件引入到我們的系統(tǒng)中,這是些以.tld為擴(kuò)展名的文件,我們要把它們放在/webapps/mystruts/WEB-INF/目錄下。引入struts標(biāo)簽后原來(lái)普通的html標(biāo)簽如文本框的標(biāo)簽變成了這樣的形式。

          Jsp文件的第二個(gè)特點(diǎn),就是頁(yè)面上根本沒有直接寫用于顯示的文字如:username,password等東西,而是用這種形式出現(xiàn)。這個(gè)特點(diǎn)為國(guó)際化編程打下了堅(jiān)實(shí)的基礎(chǔ),關(guān)于國(guó)際化編程后面的文章還會(huì)專門討論。

          這個(gè)簡(jiǎn)單的應(yīng)用所用到的ActionForm為UserInfoForm,代碼清單如下:

          package entity;
          import org.apache.struts.action.ActionForm;
          import org.apache.struts.action.ActionMapping;
          import javax.servlet.http.HttpServletRequest;
          
          public class UserInfoForm extends ActionForm{
          
            private String username;
            private String password;
          
          
            public String getUsername() {
              return (this.username);
            }
            public void setUsername(String username) {
              this.username = username;
            }
          
            public String getPassword() {
              return (this.password);
            }
            public void setPassword(String password) {
              this.password = password;
            }
          }


          在你的應(yīng)用程序的WEB-INF目錄下再建一個(gè)classes目錄,在新建的這個(gè)classes目錄下再建如下幾個(gè)目錄entity(用于存放ActionForm類)、action目錄(用于存放Action類)、bussness目錄(用于存放作為Model的業(yè)務(wù)對(duì)象類)。Classes目錄下的子目錄就是所謂的包,以后,還會(huì)根據(jù)需要增加相應(yīng)的包。

          現(xiàn)在,將UserInfoForm.java保存到entity目錄中。

          把如下代碼添加到/webapps/mystruts/WEB-INF/struts-config.xml文件中

          <form-beans>
              <form-bean name="userInfoForm" type="entity.UserInfoForm" />
            </form-beans>


          特別要提醒一下的是:關(guān)于ActionForm的大小寫,一定要按照上面的寫,以免造成不必要的麻煩。

          到此,我們完成了第一步工作。

          第二步,我們建一個(gè)名為ApplicationResource.properties的文件,并把它放在/webapps/mystruts/WEB-INF/classes目錄下。它在struts-config.xml的配置信息我們已在第一篇文章的末尾說了,就是:


          目前我們?cè)贏pplicationResource.properties文件中加入的內(nèi)容是:

          #Application Resource for the logon.jsp
          logon.jsp.title=The logon page
          logon.jsp.page.heading=Welcome World!
          logon.jsp.prompt.username=Username:
          logon.jsp.prompt.password=Password:
          logon.jsp.prompt.submit=Submit
          logon.jsp.prompt.reset=Reset
          
          #Application Resource for the main.jsp
          main.jsp.title=The main page
          main.jsp.welcome=Welcome:


          到此,我們已完成了第二個(gè)步驟。

          第三步,我們開始生成和配置Controller組件。

          在前面我們已經(jīng)提到,Struts應(yīng)用程序的控制器由org.apache.struts.action.ActionServlet和org.apache.struts.action.Action類組成,其中,前者已由Struts準(zhǔn)備好了,后者Struts只是為我們提供了個(gè)骨架,我們要做的是為實(shí)現(xiàn)應(yīng)用程序的特定功能而擴(kuò)展Action類,下面是實(shí)現(xiàn)我們登錄程序的Action類的代碼清單:

          package action;
          import java.io.IOException;
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpSession;
          import javax.servlet.http.HttpServletResponse;
          import org.apache.struts.action.Action;
          import org.apache.struts.action.ActionError;
          import org.apache.struts.action.ActionErrors;
          import org.apache.struts.action.ActionForm;
          import org.apache.struts.action.ActionForward;
          import org.apache.struts.action.ActionMapping;
          import org.apache.struts.action.ActionServlet;
          import bussness.UserInfoBo;
          import entity.UserInfoForm;
          public final class LogonAction extends Action {
            
            public ActionForward execute(ActionMapping mapping,
                   ActionForm form,
                   HttpServletRequest request,
                   HttpServletResponse response)
                   throws IOException, ServletException {
              UserInfoForm userInfoForm = (UserInfoForm) form;           
              //從web層獲得用戶名和口令
              String username = userInfoForm.getUsername().trim();
              String password = userInfoForm.getPassword().trim();
              //聲明錯(cuò)誤集對(duì)象
              ActionErrors errors = new ActionErrors();
              //校驗(yàn)輸入
              if(username.equals("")){
                ActionError error=new ActionError("error.missing.username");
                errors.add(ActionErrors.GLOBAL_ERROR,error);
              }
              if(password.equals("")){
                ActionError error=new ActionError("error.missing.password");
                errors.add(ActionErrors.GLOBAL_ERROR,error);
              }
              
              //調(diào)用業(yè)務(wù)邏輯
              if(errors.size()==0){
                String validated = "";
                try{
                  UserInfoBo userInfoBo=new UserInfoBo();
                  validated =userInfoBo.validatePwd(username,password);
                  if(validated.equals("match")){
                    //一切正常就保存用戶信息并轉(zhuǎn)向成功的頁(yè)面      
                    HttpSession session = request.getSession();
                    session.setAttribute("userInfoForm", form);          
                        return mapping.findForward("success");
                  } 
                }
                
                catch(Throwable e){
                  //處理可能出現(xiàn)的錯(cuò)誤
                  e.printStackTrace();
                  ActionError error=new ActionError(e.getMessage());
                  errors.add(ActionErrors.GLOBAL_ERROR,error);
                }
              }  
              //如出錯(cuò)就轉(zhuǎn)向輸入頁(yè)面,并顯示相應(yīng)的錯(cuò)誤信息
              saveErrors(request, errors);    
              return new ActionForward(mapping.getInput());    
            } 
          }


          這個(gè)action類中有兩個(gè)錯(cuò)誤消息鍵要加到ApplicationResource.properties文件中,清單如下:

          #Application Resource for the LogonAction.java
          error.missing.username=<li><font color="red">missing username</font></li>
          error.missing.password=<li><font color="red">missing password</font></li>>


          第四步:在struts-config.xml文件中定義Views與 Controller的關(guān)系,也就是配置所謂的ActionMapping。它們?cè)趕truts-config.xml中的位置是排在… 標(biāo)簽后,我們的登錄程序的配置清單如下:

          <action-mappings>
              <action input="/logon.jsp" name="userInfoForm" path="/logonAction" scope="session" 
                type="action.LogonAction" validate="false">
                <forward name="success" path="/main.jsp" />      
              </action>
            </action-mappings>


          第五步:生成應(yīng)用程序所需要的model組件,該組件是完成應(yīng)用程序業(yè)務(wù)邏輯的地方,現(xiàn)在我的登錄程序的業(yè)務(wù)邏輯很簡(jiǎn)單,就是判斷用戶是不是lhb并且其口令是不是awave如果是就返回一個(gè)表示匹配的字符串"match",否則,就拋出出錯(cuò)信息。其代碼清單如下:

          package bussness;
          
          import entity.UserInfoForm;
          
          public class UserInfoBo {
          
            public UserInfoBo(){
              
            }        
          
            public String validatePwd(String username,String password){
                    
              String validateResult=""; 
                 
              if(username.equals("lhb")&&password.equals("awave")){
                validateResult="match";
              }
              else{
                
                throw new RuntimeException("error.noMatch");
              }          
              
              return validateResult;   
              
            }
          }


          將其放在bussness包中。

          我們同樣要將其表示錯(cuò)誤信息的鍵值設(shè)置在ApplicationResource.properties文件中,清單如下:

          #Application Resource for the UserInfoBo.java
          error.noMatch=<li><font color="red">no matched user</font></li>


          到此為止,我們已經(jīng)完成了這個(gè)簡(jiǎn)單登錄程序的所有組件。下面就可以享受我們的勞動(dòng)成果了。

          第六步、編譯運(yùn)行應(yīng)用程序。

          常規(guī)的做法是用Ant來(lái)裝配和部署Struts應(yīng)用程序,如果按這個(gè)套路,這篇文章就會(huì)顯得十分冗長(zhǎng)乏味,同時(shí)也沒有太大的必要,因?yàn)?,用一個(gè)IDE一般可以很方便地生成一個(gè)應(yīng)用。因此,我們采用簡(jiǎn)便的方法,直接編譯我們的.java文件。不過這里要注意一點(diǎn)的是:實(shí)踐證明,要使得編譯過程不出錯(cuò),還必須將struts.jar文件放一份拷貝到/common/lib目錄中,并在環(huán)境變量中設(shè)置CLASSPATH 其值是/common/lib/struts.jar;配置好后就可以分別編譯entity、bussness及action目錄下的.java文件了。編譯完成后:打開/conf目錄下的server.xml文件,在前加上如下語(yǔ)句為我們的應(yīng)用程序建一個(gè)虛擬目錄:

          <Context path="/mystruts" docBase="mystruts" debug="0"
                           reloadable="true">                 
                      </Context>


          啟動(dòng),tomcat。在瀏覽器中輸入:http://localhost:8080/mystruts/logon.jsp
          如果前面的步驟沒有紕漏的話,一個(gè)如圖所示的登錄畫面就會(huì)出現(xiàn)在你的眼前。



          如果,不輸入任何內(nèi)容直接點(diǎn)擊Submit按鈕,就會(huì)返回到logon.jsp并顯示missing username和missing password錯(cuò)誤信息;如果輸入其他內(nèi)容,則會(huì)返回no matched user的錯(cuò)誤;如果輸入的用戶名是lhb且口令是awave則會(huì)顯示表示登錄成功的歡迎頁(yè)面。

          上面雖然是一個(gè)功能很簡(jiǎn)單的應(yīng)用程序,但麻雀雖小,五臟俱全,基本涉及到了struts的主要組成部分。下面我們就來(lái)分析一下程序的特點(diǎn)和基本的工作原理。

          首先,我們?cè)跒g覽器中輸入.jsp文件時(shí),后臺(tái)將struts的自定義標(biāo)簽"翻譯"成普通的html標(biāo)簽返回給瀏覽器,而一些提示信息如作為輸入框label的username、password還有按鈕上提示信息還有錯(cuò)誤信息等都來(lái)自MessageResources即ApplicationResource.properties文件中對(duì)應(yīng)的鍵值。當(dāng)我們點(diǎn)擊Submit按鈕時(shí),從web.xml的配置可以看出,請(qǐng)求將被ActionServlet截獲。它通過表單中提供的action參數(shù)在struts-config.xml文件中查找對(duì)應(yīng)的項(xiàng)目,如果有對(duì)應(yīng)的ActionForm,它就用表單中數(shù)據(jù)填充ActionForm的對(duì)應(yīng)屬性,本例中的ActionForm為userInfoForm,相應(yīng)的屬性是username和password,這就是所謂的實(shí)例化ActionForm。然后,將控制交給對(duì)應(yīng)的Action,本例中是LogonAction,它做的主要工作是對(duì)ActionForm中取出的username和password做了一下校驗(yàn),這里只是簡(jiǎn)單檢驗(yàn)它們是否為空(這些簡(jiǎn)單的格式化方面的校驗(yàn)應(yīng)該放在客戶端進(jìn)行,而且struts也為我們提供了一個(gè)很好的模式,后面如果有可能會(huì)詳細(xì)介紹)。如果不為空則調(diào)用判斷用戶及口令是否正確的業(yè)務(wù)邏輯模塊UserInfoBo,同時(shí),它會(huì)捕獲可能出現(xiàn)的錯(cuò)誤,然后根據(jù)業(yè)務(wù)邏輯返回的結(jié)果將程序?qū)虿煌捻?yè)面,本例中如果業(yè)務(wù)邏輯返回的結(jié)果是"match"則依據(jù)中的返回main.jsp頁(yè)面給瀏覽器同時(shí)在session對(duì)象中保存了用戶的登錄信息;否則,返回輸入頁(yè)面并顯示相應(yīng)的出錯(cuò)信息,完成了上篇文章所說的它的四個(gè)主要職責(zé)。

          大家一定注意到了,在本例的業(yè)務(wù)邏輯模塊UserInfoBo中,將用戶與密碼是寫死在程序中的,在一個(gè)真實(shí)的應(yīng)用程序中是不會(huì)這樣做的,那些需要永久保存的信息如,username及口令等都會(huì)保存在數(shù)據(jù)庫(kù)文件之類的永久介質(zhì)中,下一篇文章我們將介紹在struts中如何訪問數(shù)據(jù)庫(kù)

          (第三部分)

          一、JDBC的工作原理

          Struts在本質(zhì)上是java程序,要在Struts應(yīng)用程序中訪問數(shù)據(jù)庫(kù),首先,必須搞清楚Java Database Connectivity API(JDBC)的工作原理。正如其名字揭示的,JDBC庫(kù)提供了一個(gè)底層API,用來(lái)支持獨(dú)立于任何特定SQL實(shí)現(xiàn)的基本SQL功能。提供數(shù)據(jù)庫(kù)訪問的基本功能。它是將各種數(shù)據(jù)庫(kù)訪問的公共概念抽取出來(lái)組成的類和接口。JDBC API包括兩個(gè)包:java.sql(稱之為JDBC內(nèi)核API)和javax.sql(稱之為JDBC標(biāo)準(zhǔn)擴(kuò)展)。它們合在一起,包含了用Java開發(fā)數(shù)據(jù)庫(kù)應(yīng)用程序所需的類。這些類或接口主要有:
          Java.sql.DriverManager
          Java.sql.Driver
          Java.sql.Connection
          Java.sql.Statement
          Java.sql.PreparedStatement
          Java.sql.ResultSet等

          這使得從Java程序發(fā)送SQL語(yǔ)句到數(shù)據(jù)庫(kù)變得比較容易,并且適合所有SQL方言。也就是說為一種數(shù)據(jù)庫(kù)如Oracle寫好了java應(yīng)用程序后,沒有必要再為MS SQL Server再重新寫一遍。而是可以針對(duì)各種數(shù)據(jù)庫(kù)系統(tǒng)都使用同一個(gè)java應(yīng)用程序。這樣表述大家可能有些難以接受,我們這里可以打一個(gè)比方:聯(lián)合國(guó)開會(huì)時(shí),聯(lián)合國(guó)的成員國(guó)的與會(huì)者(相當(dāng)我們這里的具體的數(shù)據(jù)庫(kù)管理系統(tǒng))往往都有自己的語(yǔ)言(方言)。大會(huì)發(fā)言人(相當(dāng)于我們這里的java應(yīng)用程序)不可能用各種語(yǔ)言來(lái)發(fā)言。你只需要使用一種語(yǔ)言(相當(dāng)于我們這里的JDBC)來(lái)發(fā)言就行了。那么怎么保證各成員國(guó)的與會(huì)者都聽懂發(fā)言呢,這就要依靠同聲翻譯(相當(dāng)于我們這里的JDBC驅(qū)動(dòng)程序)。實(shí)際上是驅(qū)動(dòng)程序?qū)ava程序中的SQL語(yǔ)句翻譯成具體的數(shù)據(jù)庫(kù)能執(zhí)行的語(yǔ)句,再交由相應(yīng)的數(shù)據(jù)庫(kù)管理系統(tǒng)去執(zhí)行。因此,使用JDBC API訪問數(shù)據(jù)庫(kù)時(shí),我們要針對(duì)不同的數(shù)據(jù)庫(kù)采用不同的驅(qū)動(dòng)程序,驅(qū)動(dòng)程序?qū)嶋H上是適合特定的數(shù)據(jù)庫(kù)JDBC接口的具體實(shí)現(xiàn),它們一般具有如下三種功能:

        9. 建立一個(gè)與數(shù)據(jù)源的連接
        10. 發(fā)送SQL語(yǔ)句到數(shù)據(jù)源
        11. 取回結(jié)果集

          那么,JDBC具體是如何工作的呢?

          Java.sql.DriverManager裝載驅(qū)動(dòng)程序,當(dāng)Java.sql.DriverManager的getConnection()方法被調(diào)用時(shí),DriverManager試圖在已經(jīng)注冊(cè)的驅(qū)動(dòng)程序中為數(shù)據(jù)庫(kù)(也可以是表格化的數(shù)據(jù)源)的URL尋找一個(gè)合適的驅(qū)動(dòng)程序,并將數(shù)據(jù)庫(kù)的URL傳到驅(qū)動(dòng)程序的acceptsURL()方法中,驅(qū)動(dòng)程序確認(rèn)自己有連接到該URL的能力。生成的連接Connection表示與特定的數(shù)據(jù)庫(kù)的會(huì)話。Statement(包括PreparedStatement和CallableStatement)對(duì)象作為在給定Connection上執(zhí)行SQL語(yǔ)句的容器。執(zhí)行完語(yǔ)句后生成ResultSet結(jié)果集對(duì)象,通過結(jié)果集的一系列g(shù)etter就可以訪問表中各列的數(shù)據(jù)。

          這里,是講的JDBC的基本工作過程,實(shí)際應(yīng)用中,往往會(huì)使用JDBC擴(kuò)展對(duì)象如DataSource等,限于篇幅,就不在此詳細(xì)討論了。

          二、訪問數(shù)據(jù)庫(kù)所要做的基本配置

          我們以訪問MS SQL Server2000數(shù)據(jù)庫(kù)為例,介紹其基本的配置情況。首先,要到微軟網(wǎng)站去下載JDBC的驅(qū)動(dòng)程序,運(yùn)行setup.exe將得到的三個(gè)文件:msbase.jar、mssqlserver.jar及msutil.jar放在/webapps/mystruts/WEB-INF/lib目錄下。

          在struts-config.xml文件中配置數(shù)據(jù)源

          這里,有一點(diǎn)要引起大家的注意的,就是,struts-config.xml中配置的各個(gè)項(xiàng)目是有一定的順序要求的,幾個(gè)主要項(xiàng)目的順序大致是這樣的:

          data-sources
          form-beans
          action-mappings
          message-resources
          plug-in


          在配置時(shí)要遵守上述順序

          <data-sources>
              <data-source key="A" type="org.apache.commons.dbcp.BasicDataSource">
                <set-property property="driverClassName"
                    value="com.microsoft.jdbc.sqlserver.SQLServerDriver" />
                <set-property property="url"
                    value="jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=mystruts;SelectMethod=cursor" />
                <set-property property="username" value="sa" />
                <set-property property="password" value="yourpwd" />
                <set-property property="maxActive" value="10" />
                <set-property property="maxWait" value="5000" />
                <set-property property="defaultAutoCommit" value="false" />
                <set-property property="defaultReadOnly" value="false" />
              </data-source>
            </data-sources>


          我們來(lái)對(duì)這段配置代碼做一個(gè)簡(jiǎn)單的說明:

          這句中,如果您的struts應(yīng)用程序中只配置一個(gè)數(shù)據(jù)源則key="A"可以不要,而配置多個(gè)數(shù)據(jù)源時(shí)就要用這個(gè)鍵值區(qū)別,也就是說,可以為一個(gè)應(yīng)用程序配置多個(gè)數(shù)據(jù)源讓它訪問多個(gè)數(shù)據(jù)庫(kù)。

          <set-property property="url" 
                  value="jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=mystruts;
                  SelectMethod=cursor" />


          這句中的sqlserver://127.0.0.1:1433;DatabaseName=mystruts;的數(shù)據(jù)庫(kù)服務(wù)器名(本例是用代表本機(jī)的ip地址)和數(shù)據(jù)庫(kù)名稱要與您的具體情況相同。同時(shí),還要注意訪問數(shù)據(jù)庫(kù)的用戶名和口令也要合乎您的實(shí)際情況。

          表示最大的活動(dòng)連接數(shù),這也說明這些連接是池化(pooling)的。

          表示對(duì)數(shù)據(jù)庫(kù)的增、刪、改操作必須顯式地提交。即必須使用connect.commit();這樣的命令才能真正讓數(shù)據(jù)庫(kù)表中的記錄作相應(yīng)的改變。設(shè)置成這樣方便用戶組織自己的數(shù)據(jù)庫(kù)事務(wù)。

          三、現(xiàn)在我們就來(lái)擴(kuò)展前面我們講的那個(gè)登錄的例子,讓它訪問存儲(chǔ)在數(shù)據(jù)庫(kù)表中的用戶名和口令信息,同時(shí)也讓它給出的出錯(cuò)信息更明確一些。

          為此,我們先要做一些準(zhǔn)備工作,如果您還沒有安裝MS SQL Server2000請(qǐng)先安裝,并下載最新的補(bǔ)丁包。再建一個(gè)名為mystruts的數(shù)據(jù)庫(kù),并在該數(shù)據(jù)庫(kù)中建一個(gè)名為userInfo的表,該表有兩個(gè)字段既:username和password,它們的字段類型都為varchar(10),其中username為主鍵。在該表中輸入一條記錄,username和password的字段值分別為lhb和awave。到此準(zhǔn)備工作就基本做好了。

          為了訪問數(shù)據(jù)庫(kù),首先,要修改Action類,修改后的代碼清單如下:

          package action;
          import java.io.IOException;
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpSession;
          import javax.servlet.http.HttpServletResponse;
          import org.apache.struts.action.Action;
          import org.apache.struts.action.ActionError;
          import org.apache.struts.action.ActionErrors;
          import org.apache.struts.action.ActionForm;
          import org.apache.struts.action.ActionForward;
          import org.apache.struts.action.ActionMapping;
          import org.apache.struts.action.ActionServlet;
          import bussness.UserInfoBo;
          import entity.UserInfoForm;
          import javax.sql.DataSource;
          import java.sql.Connection;
          import java.sql.SQLException;
          
          public final class LogonAction extends Action {
          
            public ActionForward execute(ActionMapping mapping,
                   ActionForm form,
                   HttpServletRequest request,
                   HttpServletResponse response)
                   throws IOException, ServletException {
              UserInfoForm userInfoForm = (UserInfoForm) form;
              //從web層獲得用戶名和口令
              String username = userInfoForm.getUsername().trim();
              String password = userInfoForm.getPassword().trim();
              //聲明錯(cuò)誤集對(duì)象
              ActionErrors errors = new ActionErrors();
              //聲明數(shù)據(jù)源和連接對(duì)象
              DataSource dataSource;
              Connection cnn=null;
          
              //校驗(yàn)輸入
              if(username.equals("")){
                ActionError error=new ActionError("error.missing.username");
                errors.add(ActionErrors.GLOBAL_ERROR,error);
              }
              if(password.equals("")){
                ActionError error=new ActionError("error.missing.password");
                errors.add(ActionErrors.GLOBAL_ERROR,error);
              }
          
              //調(diào)用業(yè)務(wù)邏輯
              if(errors.size()==0){
                String validated = "";
                try{
                  //取得數(shù)據(jù)庫(kù)連接
                  dataSource = getDataSource(request,"A");
                  cnn = dataSource.getConnection();
          
                  UserInfoBo userInfoBo=new UserInfoBo(cnn);
                  validated =userInfoBo.validatePwd(username,password);
                  if(validated.equals("match")){
                    //一切正常就保存用戶信息并轉(zhuǎn)向成功的頁(yè)面
                    HttpSession session = request.getSession();
                    session.setAttribute("userInfoForm", form);
                          return mapping.findForward("success");
                  }
                }
          
                catch(Throwable e){
                  //處理可能出現(xiàn)的錯(cuò)誤
                  e.printStackTrace();
                  ActionError error=new ActionError(e.getMessage());
                  errors.add(ActionErrors.GLOBAL_ERROR,error);
                }
              }
              //如出錯(cuò)就轉(zhuǎn)向輸入頁(yè)面,并顯示相應(yīng)的錯(cuò)誤信息
              saveErrors(request, errors);
              return new ActionForward(mapping.getInput());
            }
          }


          注意:dataSource = getDataSource(request,"A");這句中,如果配置中只有一個(gè)數(shù)據(jù)源,且沒有key="A",則這句應(yīng)寫為dataSource = getDataSource(request);

          從清單上可以看出,主要就是增加了訪問數(shù)據(jù)庫(kù)的代碼。同時(shí),我們的業(yè)務(wù)對(duì)象的形式也發(fā)生了一個(gè)變化,原來(lái)沒有參數(shù),現(xiàn)在有一個(gè)代表數(shù)據(jù)庫(kù)連接的參數(shù)cnn,因此我們也要對(duì)業(yè)務(wù)對(duì)象進(jìn)行適當(dāng)?shù)匦薷摹?

          更改后的業(yè)務(wù)對(duì)象代碼清單如下:

          package bussness;
          
          import entity.UserInfoForm;
          import java.sql.Connection;
          import java.sql.SQLException;
          import java.lang.Exception;
          import db.UserInfoDao;
          
          public class UserInfoBo {
            private Connection cnn=null;
          
            public UserInfoBo(Connection cnn){
              this.cnn=cnn;
            }
          
            public String validatePwd(String username,String password){
          
              String validateResult="";
             
              try{
                UserInfoDao userInfoDao = new UserInfoDao(cnn);
                validateResult=userInfoDao.validatePwd(username,password);
                if(validateResult.equals("error.logon.invalid")){
                  //如果用戶名與口令不匹配則報(bào)此錯(cuò)
                  throw new RuntimeException("error.logon.invalid"); 
                }
                else if(validateResult.equals("error.removed.user")){
                  //如果找不到用戶則報(bào)此錯(cuò),這樣用戶看到的出錯(cuò)信息會(huì)更詳細(xì)
                  throw new RuntimeException("error.removed.user"); 
                }
              }
              catch(Exception e){
                throw new RuntimeException(e.getMessage());
              }
              finally{
                try{
                  if(cnn!=null){
                    cnn.close();
                  }
                }
                catch(SQLException sqle){
                  sqle.printStackTrace();
                  throw new RuntimeException("error.unexpected");
                }
              }
              return validateResult;
            }
          }


          這個(gè)業(yè)務(wù)對(duì)象的代碼還是比較簡(jiǎn)單的,重點(diǎn)要講的就是它在validatePwd方法中調(diào)用了一個(gè)名叫UserInfoDao的對(duì)象,它就是真正進(jìn)行數(shù)據(jù)庫(kù)操作的數(shù)據(jù)訪問對(duì)象。其代碼清單如下:

          package db;
          import entity.UserInfoForm;
          import java.sql.*;
          
          public class UserInfoDao {
            private Connection con;
          
            public UserInfoDao(Connection con) {
              this.con=con;
            }
            
            public String validatePwd(String username,String password){
              PreparedStatement ps=null;
              ResultSet rs=null;
              String validated="error.logon.invalid";
              UserInfoForm userInfoForm=null;
              String sql="select * from userInfo where username=?";
              try{
                if(con.isClosed()){
                  throw new IllegalStateException("error.unexpected");
          
                }
                ps=con.prepareStatement(sql);
                ps.setString(1,username);
                rs=ps.executeQuery();
                if(rs.next()){
                  if(!rs.getString("password").trim().equals(password)){
                    return validated;//口令不正確返回口令不匹配信息
                    
                  }
                  else{
          
                    validated = "match";//口令正確返回口令匹配信息
                    return validated;
                  }
                }else{
                  
                  validated="error.removed.user";//沒有找到該用戶
                  return validated;
                  
                }
          
              }catch(SQLException e){
                  e.printStackTrace();
                  throw new RuntimeException("error.unexpected");
              }finally{
                try{
                  if(ps!=null)
                    ps.close();
                  if(rs!=null)
                    rs.close();
                }catch(SQLException e){
                  e.printStackTrace();
                  throw new RuntimeException("error.unexpected");
                }
              }
            }
          }


          下面,簡(jiǎn)單地分析一下數(shù)據(jù)訪問對(duì)象的工作過程:

          要訪問數(shù)據(jù)庫(kù),一般要經(jīng)歷的如下幾個(gè)步驟:
        12. 獲得到數(shù)據(jù)庫(kù)的連接
        13. 創(chuàng)建SQL語(yǔ)句
        14. 執(zhí)行SQL語(yǔ)句
        15. 管理結(jié)果集

          其中,得到數(shù)據(jù)庫(kù)的連接本例中是在Action類中完成的,代碼如下:
          dataSource = getDataSource(request,"A");
          cnn = dataSource.getConnection();

          Action在調(diào)用業(yè)務(wù)對(duì)象時(shí)將連接作為一個(gè)參數(shù)傳給業(yè)務(wù)對(duì)象,再由業(yè)務(wù)對(duì)象傳給數(shù)據(jù)庫(kù)訪問對(duì)象。

          要說明一點(diǎn)的是,要將struts-legacy.jar文件放在/webapps/mystruts/WEB-INF/lib目錄下。

          我們要在/webapps/mystruts/WEB-INF/classes目錄下再建一個(gè)名叫db的子目錄,將數(shù)據(jù)訪問類以UserInfoDao.java文件名保存在該子目錄中。按照上篇文章介紹的方法,編譯各個(gè)包中的.java文件。就可以啟動(dòng)Tomcat重新運(yùn)行您的程序了。

          細(xì)心一點(diǎn)的讀者可能都注意到了,到目前為止,我們程序中的各種消息都不是用中文表示的,在下一篇文章中,我們將討論Struts的國(guó)際化編程即所謂的i18n編程,對(duì)我們?cè)诰幊讨薪?jīng)常遇到的亂碼問題也一同作些分析。

          參考文獻(xiàn):
          《JSP Web 編程指南》---電子工業(yè)出版社 Jayson Falkner等著 司光亞 牛紅等譯
          《Java數(shù)據(jù)庫(kù)編程寶典》John O'Donahue等著 甑廣啟 于耀等譯
          《Struts in Action》Ted Husted Cedric Dumoulin George Franciscus David Winterfeldt著
          《Programming Jakarta Struts》Chuck Cavaness著
          《Mastering Jakarta Struts》James Goodwill著
          《Struts Kick Start》James Turner Kevin Bedell著

          (第4部分)

          本篇我們來(lái)討論一下struts的國(guó)際化編程問題,即所謂的i18n編程問題,這一篇我們討論其基礎(chǔ)部分。與這個(gè)問題緊密相關(guān)的是在各java論壇中被頻繁提及的中文亂碼問題,因?yàn)?,英、美編程人員較少涉及到中文亂碼問題,因此,這方面的英文資料也是非常奇缺的,同時(shí)也很少找到這方面比較完整的中文資料,本文也嘗試對(duì)中文亂碼問題做一些探討。要解決上述問題,需要有一定的字符集方面的知識(shí),下面,我們就先介紹字符集的有關(guān)情況:

          一、從ASCII到Unicode(UTF-8)

          電子計(jì)算機(jī)技術(shù)是從美國(guó)開始發(fā)展起來(lái)的,因?yàn)槊绹?guó)使用的文字為英文,美國(guó)規(guī)定的計(jì)算機(jī)信息交換用的字符編碼集是人們熟知的擴(kuò)展ASCII碼,它以8bit字節(jié)為單位存儲(chǔ),ASCII的0-31及127為控制符,32-126為可見字符,包括所有的英文字母,阿拉伯?dāng)?shù)字和其他一些常見符號(hào),128-255的ASCII碼則沒有定義。

          ASCII對(duì)英語(yǔ)國(guó)家是夠用了,但對(duì)其他西歐國(guó)家卻不夠用,因此,人們將ASCII擴(kuò)展到0-255的范圍,形成了ISO-8859-1字符集。值得一提的是,因?yàn)榭紤]到程序中處理的信息大多是西文信息,因此有些WEB容器(如:Tomcat4.x)在處理所接收到的request字符串時(shí),如果您沒指定request的編碼方式則系統(tǒng)就缺省地采用ISO-8859-1,明白這一點(diǎn)對(duì)理解后面的問題會(huì)有幫助。

          相比西方的拼音文字,東方的文字(如中文)的字符數(shù)要大得多,根本不可能在一個(gè)字節(jié)內(nèi)將它們表示出來(lái),因此,它們以兩個(gè)字節(jié)為單位存儲(chǔ),以中文國(guó)標(biāo)字符集GB2312為例,它的第一個(gè)字節(jié)為128-255。系統(tǒng)可以據(jù)此判斷,若第一個(gè)字節(jié)大于127,則把與該字節(jié)后緊接著的一個(gè)字節(jié)結(jié)合起來(lái)共兩個(gè)字節(jié)組成一個(gè)中文字符。這種由多個(gè)字節(jié)存儲(chǔ)一個(gè)字符的字符集叫多字節(jié)字符集(MultiByte Charsets),對(duì)應(yīng)的象ASCII這種用一個(gè)字節(jié)存儲(chǔ)一個(gè)字符的字符集叫單字節(jié)字符集(SingleByte Charsets)。在GB2312字符集中,ASCII字符仍然用一個(gè)字節(jié)存儲(chǔ),換句話說該ASCII是該字符集的子集。

          GB2312只包含數(shù)千個(gè)常用漢字,往往不能滿足實(shí)際需要,因此,人們對(duì)它進(jìn)行擴(kuò)展,這就有了我們現(xiàn)在廣泛使用的GBK字符集,GBK是現(xiàn)階段Windows及其他一些中文操作系統(tǒng)的缺省字符集。它包含2萬(wàn)多個(gè)字符,除了保持和GB2312兼容外,還包含繁體中文字,日文字符和朝鮮字符。值得注意的是GBK只是一個(gè)規(guī)范而不是國(guó)家標(biāo)準(zhǔn),新的國(guó)家標(biāo)準(zhǔn)是GB18030-2000,它是比GBK包含字符更多的字符集。

          我國(guó)的臺(tái)灣地區(qū)使用的文字是繁體字,其字符集是BIG5,而日本采用的字符集則是SJIS。它們的編碼方法與GB2312類似,它們的ASCII字符部分是兼容的,但擴(kuò)展部分的編碼則是不兼容的,比如這幾種字符集中都有"中文"這兩個(gè)字符,但他們?cè)诟髯缘淖址械木幋a并不相同,這就是用GB2312寫成的網(wǎng)頁(yè)用BIG5瀏覽時(shí),看到的是亂糟糟的信息的原因。

          可見,在字符集的世界里,呈現(xiàn)給我們的是一個(gè)群雄割據(jù)的局面,各字符集擁有一塊自己的地盤。這給各國(guó)和各地區(qū)交換信息帶來(lái)了很大的困難,同時(shí),也給國(guó)際化(本地化)編程造成了很大的麻煩。

          常言道:"分久必合",隨著國(guó)際標(biāo)準(zhǔn)ISO10646定義的通用字符集(Universal Character Set即UCS)的出現(xiàn),使這種局面發(fā)生了徹底的改觀。UCS 是所有其他字符集標(biāo)準(zhǔn)的一個(gè)超集. 它保證與其他字符集是雙向兼容的. 就是說, 如果你將任何文本字符串翻譯到 UCS格式, 然后再翻譯回原編碼, 你不會(huì)丟失任何信息。UCS 包含了用于表達(dá)所有已知語(yǔ)言的字符。不僅包括拉丁語(yǔ)、希臘語(yǔ)、 斯拉夫語(yǔ)、希伯來(lái)語(yǔ)、阿拉伯語(yǔ)、亞美尼亞語(yǔ)和喬治亞語(yǔ)的描述、還包括中文、 日文和韓文這樣的象形文字、 以及平假名、片假名、 孟加拉語(yǔ)、 旁遮普語(yǔ)果魯穆奇字符(Gurmukhi)、 泰米爾語(yǔ)、印.埃納德語(yǔ)(Kannada)、Malayalam、泰國(guó)語(yǔ)、 老撾語(yǔ)、 漢語(yǔ)拼音(Bopomofo)、Hangul、 Devangari、Gujarati、Oriya、Telugu 以及其他數(shù)也數(shù)不清的語(yǔ)。對(duì)于還沒有加入的語(yǔ)言, 由于正在研究怎樣在計(jì)算機(jī)中最好地編碼它們, 因而最終它們都將被加入。

          ISO 10646 定義了一個(gè) 31 位的字符集。 然而, 在這巨大的編碼空間中, 迄今為止只分配了前 65534 個(gè)碼位 (0x0000 到 0xFFFD)。 這個(gè) UCS 的 16位子集稱為 基本多語(yǔ)言面 (Basic Multilingual Plane, BMP)。 將被編碼在 16 位 BMP 以外的字符都屬于非常特殊的字符(比如象形文字), 且只有專家在歷史和科學(xué)領(lǐng)域里才會(huì)用到它們。

          UCS 不僅給每個(gè)字符分配一個(gè)代碼, 而且賦予了一個(gè)正式的名字。 表示一個(gè) UCS 值的十六進(jìn)制數(shù), 通常在前面加上 "U+", 就象 U+0041 代表字符"拉丁大寫字母A"。 UCS 字符 U+0000 到 U+007F 與 US-ASCII(ISO 646) 是一致的, U+0000 到 U+00FF 與 ISO 8859-1(Latin-1) 也是一致的。這里要注意的是它是以16bit為單位存儲(chǔ),即便對(duì)字母"A"也是用16bit,這是與前面介紹的所有字符集不同的地方。

          歷史上,在國(guó)際標(biāo)準(zhǔn)化組織研究ISO10646標(biāo)準(zhǔn)的同時(shí),另一個(gè)由多語(yǔ)言軟件制造商組成的協(xié)會(huì)也在從事創(chuàng)立單一字符集的工作,這就是現(xiàn)在人們熟知的Unicode。幸運(yùn)的是,1991年前后ISO10646和Unicode的參與者都認(rèn)識(shí)到,世界上不需要兩個(gè)不同的單一字符集。他們合并雙方的工作成果,并為創(chuàng)立單一編碼表而協(xié)同工作。兩個(gè)項(xiàng)目仍都存在并獨(dú)立地公布各自的標(biāo)準(zhǔn),都同意保持ISO10646和Unicode的碼表兼容,并緊密地共同調(diào)整任何未來(lái)的擴(kuò)展。這與當(dāng)年在PC機(jī)上的操作系統(tǒng)MS-dos與PC-dos的情形有些相象。后面,我們將視ISO10646和Unicode為同一個(gè)東西。

          有了Unicode,字符集問題接近了完美的解決,但不要高興得過早。由于歷史的原因:一些操作系統(tǒng)如:Unix、Linux等都是基于ASCII設(shè)計(jì)的。此外,還有一些數(shù)據(jù)庫(kù)管理系統(tǒng)軟件如:Oracle等也是圍繞ASCII來(lái)設(shè)計(jì)的(從其8i的白皮書上介紹的設(shè)置系統(tǒng)字符集和字段的字符集中可以間接地看到這一點(diǎn))。在這些系統(tǒng)中直接用Unicode會(huì)導(dǎo)致嚴(yán)重的問題。用這些編碼的字符串會(huì)包含一些特殊的字符, 比如 '\0' 或 '/', 它們?cè)?文件名和其他 C 庫(kù)函數(shù)參數(shù)里都有特別的含義。 另外, 大多數(shù)使用 ASCII 文件的 UNIX 下的工具, 如果不進(jìn)行重大修改是無(wú)法讀取 16 位的字符的。 基于這些原因, 在文件名, 文本文件, 環(huán)境變量等地方,直接使用Unicode是不合適的。

          在 ISO 10646-1 Annex R 和 RFC 2279 里定義的 UTF-8 (Unicode Transformation Form 8-bit form)編碼沒有這些問題。

          UTF-8 有以下一些特性:

          UCS 字符 U+0000 到 U+007F (ASCII) 被編碼為字節(jié) 0x00 到 0x7F (ASCII 兼容)。 這意味著只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 兩種編碼方式下是一樣的。

          所有 >U+007F 的 UCS 字符被編碼為一個(gè)多個(gè)字節(jié)的串, 每個(gè)字節(jié)都有標(biāo)記位集。 因此,ASCII 字節(jié) (0x00-0x7F) 不可能作為任何其他字符的一部分。

          表示非 ASCII 字符的多字節(jié)串的第一個(gè)字節(jié)總是在 0xC0 到 0xFD 的范圍里, 并指出這個(gè)字符包含多少個(gè)字節(jié)。 多字節(jié)串的其余字節(jié)都在 0x80 到 0xBF 范圍里。 這使得重新同步非常容易, 并使編碼無(wú)國(guó)界,且很少受丟失字節(jié)的影響。

          UTF-8 編碼字符理論上可以最多到 6 個(gè)字節(jié)長(zhǎng), 然而 16 位 BMP 字符最多只用到 3 字節(jié)長(zhǎng)。

          字節(jié) 0xFE 和 0xFF 在 UTF-8 編碼中從未用到。

          通過,UTF-8這種形式,Unicode終于可以廣泛的在各種情況下使用了。在討論struts的國(guó)際化編程之前,我們先來(lái)看看我們以前在jsp編程中是怎樣處理中文問題以及我們經(jīng)常遇到的:

          二、中文字符亂碼的原因及解決辦法

          java的內(nèi)核是Unicode的,也就是說,在程序處理字符時(shí)是用Unicode來(lái)表示字符的,但是文件和流的保存方式是使用字節(jié)流的。在java的基本數(shù)據(jù)類型中,char是Unicode的,而byte是字節(jié),因此,在不同的環(huán)節(jié)java要對(duì)字節(jié)流和char進(jìn)行轉(zhuǎn)換。這種轉(zhuǎn)換發(fā)生時(shí)如果字符集的編碼選擇不當(dāng),就會(huì)出現(xiàn)亂碼問題。

          我們常見的亂碼大致有如下幾種情形:
          1、漢字變成了問號(hào)"?"
          2、有的漢字顯示正確,有的則顯示錯(cuò)誤
          3、顯示亂碼(有些是漢字但并不是你預(yù)期的)
          4、讀寫數(shù)據(jù)庫(kù)出現(xiàn)亂碼

          下面我們逐一對(duì)它們出現(xiàn)的原因做一些解釋:

          首先,我們討論漢字變成問號(hào)的問題。

          Java中byte與char相互轉(zhuǎn)換的方法在sun.io包中。其中,byte到char的常用轉(zhuǎn)換方法是:
          public static ByteToCharConverter getConverter(String encoding);

          為了便于大家理解,我們先來(lái)做一個(gè)小實(shí)驗(yàn):比如,漢字"你"的GBK編碼為0xc4e3,其Unicode編碼是\u4f60。我們的實(shí)驗(yàn)是這樣的,先有一個(gè)頁(yè)面比如名為a_gbk.jsp輸入漢字"你",提交給頁(yè)面b_gbk.jsp。在b_gbk.jsp文件中以某種編碼方式得到"你"的字節(jié)數(shù)組,再將該數(shù)組以某種編碼方式轉(zhuǎn)換成char,如果得到的char值是0x4f60則轉(zhuǎn)換是正確的。

          a_gbk.jsp的代碼如下:

          <%@ page contentType="text/html; charset=GBK" language="java" import="java.sql.*" errorPage="" %>
          <table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
            <tr>
              <td>&nbsp;</td>
              <td class="bigword">&nbsp;</td>
              <td>&nbsp;</td>
            </tr>
            <tr>
              <td width="100">&nbsp;</td>
              <td class="bigword">Input</td>
              <td width="100">&nbsp;</td>
            </tr>
            <tr>
              <td>&nbsp;</td>
              <td class="bigword">&nbsp;</td>
              <td>&nbsp;</td>
            </tr>
          </table>
          <table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
            <tr>
              <td><form method="post" action="b_gbk.jsp">
                  <table width="611" border="0" cellpadding="0" cellspacing="0">
                    <tr>
                      <td width="100" align="right"></td>
                      <td><input name="ClsID" type="text" class="word" id="ClsID" maxlength="2" >
                        *</td>
                    </tr>
                    <tr>
                      <td width="100" align="right">&nbsp;</td>
                      <td><input name="btn" type="submit" value="OK">
                       </td>
                    </tr>
                  </table>
                </form></td>
            </tr>
          </table>



          b_gbk.jsp的代碼如下:

          <%@ page contentType="text/html; charset=GBK" import="sun.io.*,java.util.*" %>
          <%
          String a=(String)request.getParameter("ClsID");
          byte b[]=a.getBytes("ISO8859-1");
          for(int j=0;j<b.length;j++){
            out.println(Integer.toHexString(b[j])+"<br>");
          }
          ByteToCharConverter convertor=ByteToCharConverter.getConverter("GBK");
          char[] c=convertor.convertAll(b);
          out.println("b length:"+b.length+"<br>");
          out.println("c length:"+c.length+"<br>");
          for(int i=0;i<c.length;i++){
                 out.println(Integer.toHexString(c[i])+"<br>");
          }
          String a1=new String(a.getBytes("ISO8859-1"),"GBK");
          %>
          <%="a是:"+a%><br>
          <%="a1是:"+a1%>



          在瀏覽器中打開a_gbk.jsp并輸入一個(gè)"你"字,點(diǎn)擊OK按鈕提交表單,則會(huì)出現(xiàn)如圖1所示的結(jié)果:



          圖1

          從圖1可以看出,在b_gbk.jsp中這樣將byte轉(zhuǎn)換為char是正確的,即得到的char是\u4f60。這里要注意的是:byte b[]=a.getBytes("ISO8859-1");中的編碼是ISO8859-1,這就是我們前面提到的有些web容器在您沒有指定request的字符集時(shí)它就采用缺省的ISO8859-1。

          從圖1中我們還看到表達(dá)式<%="a是:"+a%>中的a并沒有正確地顯示"你"而是變成"??"這是什么原因呢?這里的a是作為一個(gè)String被顯示的,我們來(lái)看看我們常用的String構(gòu)造函數(shù):

          String(byte[] bytes,String encoding);

          在國(guó)標(biāo)平臺(tái)上,該函數(shù)會(huì)認(rèn)為bytes是按GBK編碼的,如果后一個(gè)參數(shù)省略,它也會(huì)認(rèn)為是encoding是GBK。

          對(duì)前一個(gè)參數(shù)就相當(dāng)于將b_gbk.jsp文件的這句byte b[]=a.getBytes("ISO8859-1");中的ISO8859-1改為GBK,這樣顯然在GBK字符集中找不到相應(yīng)的目的編碼,它給出的結(jié)果是0x3f、0x3f。因此,就會(huì)顯示為"??",這也就是造成亂碼的第一種現(xiàn)象的原因。我們的例子是演示的從byte到char的轉(zhuǎn)換過程,相反的過程也會(huì)造成同樣的問題,限于篇幅,就不在此討論了,大家自己可以做類似的實(shí)驗(yàn)來(lái)驗(yàn)證。

          解決該問題的方法就是象例子中a1那樣,在獲取byte數(shù)組時(shí),指定編碼為ISO8859-1。

          接下來(lái),我們討論有些漢字能正常顯示,有些不能正常顯示的問題。

          如果我們將String a1=new String(a.getBytes("ISO8859-1"),"GBK");中的GBK改為GB2312則象朱镕基的"镕"字就不能正常顯示,這是因?yàn)樵撟质荊BK中的字符而在GB2312中不存在。

          解決上述兩種問題的方法就是象a1那樣構(gòu)造String,也就是人們常說的同時(shí)也是常用的轉(zhuǎn)碼的方法。采用這種方法會(huì)在程序中到處出現(xiàn)這種語(yǔ)句,特別是在Struts中,Struts有一個(gè)回寫表單的功能,在回寫時(shí)也要做這種轉(zhuǎn)換,這樣的語(yǔ)句差不多要多一倍。因此,這是個(gè)比較笨拙的方法,有沒有簡(jiǎn)捷一些的方法呢?其實(shí)是有的,只要在取得request的字符串前加上request.setCharacterEncoding("GBK");這句,指定request的字符集。則<%="a是:"+a%>中的a就能正常顯示,a1反而不能正常顯示。此時(shí)要將byte b[]=a.getBytes("ISO8859-1");中的ISO8859-1變成GBK,從byte到char的轉(zhuǎn)換才是正確的,這就是此時(shí)a能正常顯示而a1反而不能正常顯示的原因。如果此時(shí)要a1正常顯示則必須將String a1=new String(a.getBytes("ISO8859-1"),"GBK");中的ISO8859-1改為GBK。

          很顯然,使用request.setCharacterEncoding("GBK");只能解決GBK字符問題,要解決i18n問題則要使用UTF-8來(lái)取代GBK。我們接著做上述實(shí)驗(yàn),將a_gbk.jsp和b_gbk.jsp分別另存為a.jsp和b.jsp將文件中的GBK改為UTF-8,更改后的代碼分別如下:

          a.jsp代碼:

          <%@ page contentType="text/html; charset=UTF-8" language="java" import="java.sql.*" errorPage="" %>
          
          <table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
            <tr>
              <td>&nbsp;</td>
              <td class="bigword">&nbsp;</td>
              <td>&nbsp;</td>
            </tr>
            <tr>
              <td width="100">&nbsp;</td>
              <td class="bigword">Input</td>
              <td width="100">&nbsp;</td>
            </tr>
            <tr>
              <td>&nbsp;</td>
              <td class="bigword">&nbsp;</td>
              <td>&nbsp;</td>
            </tr>
          </table>
          <table width="611" border="0" align="center" cellpadding="0" cellspacing="0">
            <tr>
              <td><form method="post" action="b.jsp">
                  <table width="611" border="0" cellpadding="0" cellspacing="0">
                    <tr>
                      <td width="100" align="right"></td>
                      <td><input name="ClsID" type="text" class="word" id="ClsID" maxlength="2" >
                        *</td>
                    </tr>
                    <tr>
                      <td width="100" align="right">&nbsp;</td>
                      <td><input name="btn" type="submit" value="OK">
                       </td>
                    </tr>
                  </table>
                </form></td>
            </tr>
          </table>
          b.jsp代碼:
          <ccid_nobr>
          <table width="400" border="1" cellspacing="0" cellpadding="2" 
           bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center">
          <tr>
              <td bgcolor="e6e6e6" class="code" style="font-size:9pt">
              <pre><ccid_code>  <%@ page contentType="text/html; charset=UTF-8" import="sun.io.*,java.util.*" %>
          
          <%
          request.setCharacterEncoding("UTF-8");
          String a=(String)request.getParameter("ClsID");
          byte b[]=a.getBytes("UTF-8");
          for(int j=0;j<b.length;j++){
            out.println(Integer.toHexString(b[j])+"<br>");
          }
          ByteToCharConverter convertor=ByteToCharConverter.getConverter("UTF-8");
          char[] c=convertor.convertAll(b);
          out.println("b length:"+b.length+"<br>");
          out.println("c length:"+c.length+"<br>");
          for(int i=0;i<c.length;i++){
            out.println(Integer.toHexString(c[i])+"<br>");
          }
          String a1=new String(a.getBytes("UTF-8"),"UTF-8");
          %>
          <%="a是:"+a%><br>
          <%="a1是:"+a1%>



          再在a.jsp中輸入"你"字,你會(huì)發(fā)現(xiàn)顯示結(jié)果中,一個(gè)漢字是用三個(gè)byte表示的,它們的值分別是0xe4、0xbd、0xa0,也就是說用UTF-8來(lái)表示漢字,每個(gè)漢字要比GBK多占用一個(gè)byte,這也是使用UTF-8要多付出的一點(diǎn)代價(jià)吧。

          現(xiàn)在,我們討論一下第三個(gè)問題,即顯示亂碼,有些莫名其妙的漢字并不是你預(yù)期的結(jié)果。

          在上例中將String a1=new String(a.getBytes("UTF-8"),"UTF-8");改為String a1=new String(a.getBytes("UTF-8"),"GBK");再輸入"你"字,則a1會(huì)顯示成"浣?",您只要看一看"浣"的UTF-8碼和GBK碼就會(huì)知道其中的奧秘了。

          下面,我們討論一下最后一個(gè)問題,就是讀寫數(shù)據(jù)庫(kù)時(shí)出現(xiàn)亂碼。

          現(xiàn)在一些常用的數(shù)據(jù)庫(kù)都支持?jǐn)?shù)據(jù)庫(kù)encoding,也就是說在創(chuàng)建數(shù)據(jù)庫(kù)時(shí)可以指定它自己的字符集設(shè)置,數(shù)據(jù)庫(kù)數(shù)據(jù)以指定的編碼形式存儲(chǔ)。當(dāng)應(yīng)用程序訪問數(shù)據(jù)庫(kù)時(shí),在入口和出口處都會(huì)有encoding轉(zhuǎn)換。如果,在應(yīng)用程序中字符本來(lái)已變成了亂碼,當(dāng)然也就無(wú)法正確地轉(zhuǎn)換為數(shù)據(jù)庫(kù)的字符集了。數(shù)據(jù)庫(kù)的encoding可根據(jù)需要來(lái)設(shè)置,比如要支持簡(jiǎn)、繁體中文、日、韓、英語(yǔ)選GBK,如果還要支持其他語(yǔ)言最好選UTF-8。

          本篇文章對(duì)字符集及中文亂碼問題做了一下探討,為實(shí)現(xiàn)國(guó)際化編程的實(shí)踐打下一個(gè)基礎(chǔ)。下一篇文章,我們將介紹struts中實(shí)現(xiàn)國(guó)際化編程的具體步驟,并將我們前面介紹的登錄例子進(jìn)行國(guó)際化。

          參考文獻(xiàn):

          UTF-8 and Unicode FAQ

          《JSP動(dòng)態(tài)網(wǎng)站技術(shù)入門與提高》太陽(yáng)工作室 孫曉龍 趙莉編著

          第5部分

          一個(gè)支持i18n的應(yīng)用程序應(yīng)該有如下一些特征:
          1增加支持的語(yǔ)言時(shí)要求不更改程序代碼
          2字符元素、消息、和圖象保存在原代碼之外
          3依賴于不同文化的數(shù)據(jù)如:日期時(shí)間、小數(shù)、及現(xiàn)金符號(hào)等數(shù)據(jù)對(duì)用戶的語(yǔ)言和地理位置應(yīng)該有正確的格式
          4應(yīng)用程序能迅速地適應(yīng)新語(yǔ)言和/或新地區(qū)

          Struts主要采用兩個(gè)i18n組件來(lái)實(shí)現(xiàn)國(guó)際化編程:

          第一個(gè)組件是一個(gè)被應(yīng)用程序控制器管理的消息類,它引用包含地區(qū)相關(guān)信息串的資源包。第二個(gè)組件是一個(gè)JSP定制標(biāo)簽,,它用于在View層呈現(xiàn)被控制器管理的實(shí)際的字符串。在我們前面的登錄例子中這兩方面的內(nèi)容都出現(xiàn)過。

          用Struts實(shí)現(xiàn)國(guó)際化編程的標(biāo)準(zhǔn)做法是:生成一個(gè)java屬性文件集。每個(gè)文件包含您的應(yīng)用程序要顯示的所有消息的鍵/值對(duì)。

          這些文件的命名要遵守如下規(guī)則,代表英文消息的文件可作為缺省的文件,它的名稱是ApplicationResources.properties;其他語(yǔ)種的文件在文件名中都要帶上相應(yīng)的地區(qū)和語(yǔ)言編碼串,如代表中文的文件名應(yīng)為ApplicationResources_zh_CN.properties。并且其他語(yǔ)種的文件與ApplicationResources.properties文件要放在同一目錄中。

          ApplicationResources.properties文件的鍵/值都是英文的,而其他語(yǔ)種文件的鍵是英文的,值則是對(duì)應(yīng)的語(yǔ)言。如在我們前面的登錄例子中的鍵/值對(duì):logon.jsp.prompt.username=Username:在中文文件中就是:logon.jsp.prompt.username=用戶名:當(dāng)然,在實(shí)際應(yīng)用時(shí)要把中文轉(zhuǎn)換為AscII碼。

          有了上一篇文章和以上介紹的一些基礎(chǔ)知識(shí)后。我們就可以將我們的登錄程序進(jìn)行國(guó)際化編程了。

          首先,我們所有jsp頁(yè)面文件的字符集都設(shè)置為UTF-8。即在頁(yè)面文件的開始寫如下指令行:

          <%@ page contentType="text/html; charset=UTF-8" %>,在我們的登錄例子中已經(jīng)這樣做了,這里不需要再改動(dòng)。

          其次,將所有的request的字符集也設(shè)置為UTF-8。雖然,我們可以在每個(gè)文件中加入這樣的句子:request.setCharacterEncoding("UTF-8");來(lái)解決,但這樣顯得很麻煩。一種更簡(jiǎn)單的解決方法是使用filter。具體步驟如下:

          在mystruts\WEB-INF\classes目錄下再新建一個(gè)名為filters的目錄,新建一個(gè)名為:SetCharacterEncodingFilter的類,并保存在該目錄下。其實(shí),這個(gè)類并不要您親自來(lái)寫,可以借用tomcat中的例子?,F(xiàn)將該例子的程序節(jié)選如下:

          package filters;
          import java.io.IOException;
          import javax.servlet.Filter;
          import javax.servlet.FilterChain;
          import javax.servlet.FilterConfig;
          import javax.servlet.ServletException;
          import javax.servlet.ServletRequest;
          import javax.servlet.ServletResponse;
          import javax.servlet.UnavailableException;
          
          /**
           * <p>Example filter that sets the character encoding to be used in parsing the
           * incoming request, either unconditionally or only if the client did not
           * specify a character encoding.  Configuration of this filter is based on
           * the following initialization parameters:</p>
           * <ul>
           * <li><strong>encoding</strong> - The character encoding to be configured
           *     for this request, either conditionally or unconditionally based on
           *     the <code>ignore</code> initialization parameter.  This parameter
           *     is required, so there is no default.</li>
           * <li><strong>ignore</strong> - If set to "true", any character encoding
           *     specified by the client is ignored, and the value returned by the
           *     <code>selectEncoding()</code> method is set.  If set to "false,
           *     <code>selectEncoding()</code> is called <strong>only</strong> if the
           *     client has not already specified an encoding.  By default, this
           *     parameter is set to "true".</li>
           * </ul>
           *
           * <p>Although this filter can be used unchanged, it is also easy to
           * subclass it and make the <code>selectEncoding()</code> method more
           * intelligent about what encoding to choose, based on characteristics of
           * the incoming request (such as the values of the <code>Accept-Language</code>
           * and <code>User-Agent</code> headers, or a value stashed in the current
           * user's session.</p>
           *
           * @author Craig McClanahan
           * @version $Revision: 1.2 $ $Date: 2001/10/17 22:53:19 $
           */
          
          public class SetCharacterEncodingFilter implements Filter {
          
          
              // ----------------------------------------------------- Instance Variables
          
          
              /**
               * The default character encoding to set for requests that pass through
               * this filter.
               */
              protected String encoding = null;
          
          
              /**
               * The filter configuration object we are associated with.  If this value
               * is null, this filter instance is not currently configured.
               */
              protected FilterConfig filterConfig = null;
          
          
              /**
               * Should a character encoding specified by the client be ignored?
               */
              protected boolean ignore = true;
          
          
              // --------------------------------------------------------- Public Methods
          
          
              /**
               * Take this filter out of service.
               */
              public void destroy() {
          
                  this.encoding = null;
                  this.filterConfig = null;
          
              }
          
          
              /**
               * Select and set (if specified) the character encoding to be used to
               * interpret request parameters for this request.
               *
               * @param request The servlet request we are processing
               * @param result The servlet response we are creating
               * @param chain The filter chain we are processing
               *
               * @exception IOException if an input/output error occurs
               * @exception ServletException if a servlet error occurs
               */
              public void doFilter(ServletRequest request, ServletResponse response,
                                   FilterChain chain)
                  throws IOException, ServletException {
          
                  // Conditionally select and set the character encoding to be used
                  if (ignore || (request.getCharacterEncoding() == null)) {
                      String encoding = selectEncoding(request);
                      if (encoding != null)
                          request.setCharacterEncoding(encoding);
                  }
          
                  // Pass control on to the next filter
                  chain.doFilter(request, response);
          
              }
          
          
              /**
               * Place this filter into service.
               *
               * @param filterConfig The filter configuration object
               */
              public void init(FilterConfig filterConfig) throws ServletException {
          
                  this.filterConfig = filterConfig;
                  this.encoding = filterConfig.getInitParameter("encoding");
                  String value = filterConfig.getInitParameter("ignore");
                  if (value == null)
                      this.ignore = true;
                  else if (value.equalsIgnoreCase("true"))
                      this.ignore = true;
                  else if (value.equalsIgnoreCase("yes"))
                      this.ignore = true;
                  else
                      this.ignore = false;
          
              }
          
          
              // ------------------------------------------------------ Protected Methods
          
          
              /**
               * Select an appropriate character encoding to be used, based on the
               * characteristics of the current request and/or filter initialization
               * parameters.  If no character encoding should be set, return
               * <code>null</code>.
               * <p>
               * The default implementation unconditionally returns the value configured
               * by the <strong>encoding</strong> initialization parameter for this
               * filter.
               *
               * @param request The servlet request we are processing
               */
              protected String selectEncoding(ServletRequest request) {
          
                  return (this.encoding);
          
              }
          
          }



          其中,request.setCharacterEncoding(encoding);是一個(gè)關(guān)鍵句子。

          為了讓該類工作,我們還要在web.xml文件中對(duì)它進(jìn)行配置,配置代碼如下:

          <filter>
              <filter-name>Set Character Encoding</filter-name>
              <filter-class>filters.SetCharacterEncodingFilter</filter-class>
              <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
              </init-param>
            </filter>
            <filter-mapping>
              <filter-name>Set Character Encoding</filter-name>
              <url-pattern>/*</url-pattern>
            </filter-mapping>



          最后,就是準(zhǔn)備資源包文件,我們以創(chuàng)建一個(gè)中文文件為例:

          將ApplicationResources.properties文件打開,另存為ApplicationResources_zh.properties,這只是一個(gè)過渡性質(zhì)的文件。將文件中鍵/值對(duì)的值都用中文表示。更改完后的代碼如下:

          #Application Resource for the logon.jsp
          logon.jsp.title=登錄頁(yè)
          logon.jsp.page.heading=歡迎 世界!
          logon.jsp.prompt.username=用戶名:
          logon.jsp.prompt.password=口令:
          logon.jsp.prompt.submit=提交
          logon.jsp.prompt.reset=復(fù)位
          
          #Application Resource for the main.jsp
          main.jsp.title=主頁(yè)
          main.jsp.welcome=歡迎:
          
          #Application Resource for the LogonAction.java
          error.missing.username=<li><font color="red">沒有輸入用戶名</font></li>
          error.missing.password=<li><font color="red">沒有輸入口令</font></li>
          
          #Application Resource for the UserInfoBo.java
          error.noMatch=<li><font color="red">沒有匹配的用戶</font></li>
          
          #Application Resource for the UserInfoBo.java
          error.logon.invalid=<li><font color="red">用戶名/口令是無(wú)效的</font></li>
          error.removed.user=<li><font color="red">找不到該用戶</font></li>
          error.unexpected=<li><font color="red">不可預(yù)期的錯(cuò)誤</font></li>



          使用native2ascii工具將上面文件中的中文字符轉(zhuǎn)換為ascii碼,并生成一個(gè)最終使用的資源文件ApplicationResources_zh_CN.properties。

          具體做法是打開一個(gè)dos窗口,到mystruts\WEB-INF\classes目錄下,運(yùn)行如下語(yǔ)句:

          native2ascii -encoding GBK ApplicationResources_zh.properties ApplicationResources_zh_CN.properties

          生成的文件ApplicationResources_zh_CN.properties的內(nèi)容如下:

          #Application Resource for the logon.jsp
          logon.jsp.title=\u767b\u5f55\u9875
          logon.jsp.page.heading=\u6b22\u8fce \u4e16\u754c!
          logon.jsp.prompt.username=\u7528\u6237\u540d:
          logon.jsp.prompt.password=\u53e3\u4ee4:
          logon.jsp.prompt.submit=\u63d0\u4ea4
          logon.jsp.prompt.reset=\u590d\u4f4d
          
          #Application Resource for the main.jsp
          main.jsp.title=\u4e3b\u9875
          main.jsp.welcome=\u6b22\u8fce:
          
          #Application Resource for the LogonAction.java
          error.missing.username=<li><font color="red">\u6ca1\u6709\u8f93\u5165\u7528\u6237\u540d</font></li>
          error.missing.password=<li><font color="red">\u6ca1\u6709\u8f93\u5165\u53e3\u4ee4</font></li>
          
          #Application Resource for the UserInfoBo.java
          error.noMatch=<li><font color="red">\u6ca1\u6709\u5339\u914d\u7684\u7528\u6237</font></li>
          
          #Application Resource for the UserInfoBo.java
          error.logon.invalid=<li><font color="red">\u7528\u6237\u540d/\u53e3\u4ee4\u662f\u65e0\u6548\u7684</font></li>
          error.removed.user=<li><font color="red">\u627e\u4e0d\u5230\u8be5\u7528\u6237</font></li>
          error.unexpected=<li><font color="red">\u4e0d\u53ef\u9884\u671f\u7684\u9519\u8bef</font></li>



          從這里可以看出,所有的中文字都轉(zhuǎn)換成了對(duì)應(yīng)的Unicode碼。

          現(xiàn)在,再運(yùn)行登錄例子程序,您會(huì)發(fā)現(xiàn)它已經(jīng)是顯示的中文了。在瀏覽器的"工具"--"Internet選項(xiàng)"的"語(yǔ)言首選項(xiàng)"對(duì)話框中,去掉"中文(中國(guó))"加上英文,再試登錄程序,此時(shí),又會(huì)顯示英文。這就是說不同國(guó)家(地區(qū))的客戶都可以看到自己語(yǔ)言的內(nèi)容,這就實(shí)現(xiàn)了國(guó)際化編程的基本要求。如果還要顯示其他語(yǔ)言,可采用類似處理中文的方法進(jìn)行,這里就不細(xì)講了。

          本文中的例子程序所采用的數(shù)據(jù)庫(kù)仍然是MS SQLServer2000,數(shù)據(jù)庫(kù)字符集為gbk。實(shí)驗(yàn)表明,對(duì)簡(jiǎn)、繁體中文,英文及日文字符都能支持。

          參考文獻(xiàn):
          《Programming Jakarta Struts》Chuck Cavaness著
          《Mastering Jakarta Struts》James Goodwill著

          第6部分

          本文我們來(lái)討論一下Struts中的輸入校驗(yàn)問題。我們知道,信息系統(tǒng)有垃圾進(jìn)垃圾出的特點(diǎn),為了避免垃圾數(shù)據(jù)的輸入,對(duì)輸入進(jìn)行校驗(yàn)是任何信息系統(tǒng)都要面對(duì)的問題。在傳統(tǒng)的編程實(shí)踐中,我們往往在需要進(jìn)行校驗(yàn)的地方分別對(duì)它們進(jìn)行校驗(yàn),而實(shí)際上需要校驗(yàn)的東西大多都很類似,如必需的字段、日期、范圍等等。因此,應(yīng)用程序中往往到處充斥著這樣一些顯得冗余的代碼。而與此形成鮮明對(duì)照的是Struts采用Validator框架(Validator框架現(xiàn)在是Jakarta Commons項(xiàng)目的一部分)來(lái)解決校驗(yàn)問題,它將校驗(yàn)規(guī)則代碼集中到外部的且對(duì)具體的應(yīng)用程序中立的.xml文件中,這樣,就將那些到處出現(xiàn)的校驗(yàn)邏輯從應(yīng)用程序中分離出來(lái),任何一個(gè)Struts應(yīng)用都可以使用這個(gè)文件,同時(shí)還為校驗(yàn)規(guī)則的擴(kuò)展提供了便利。更難能可貴的是由于Validator框架將校驗(yàn)中要用到的一些消息等信息與資源綁定有機(jī)結(jié)合在一起,使得校驗(yàn)部分的國(guó)際化編程變得十分的便捷和自然。

              Validator框架大致有如下幾個(gè)主要組件:

              Validators:是Validator框架調(diào)用的一個(gè)Java類,它處理那些基本的通用的校驗(yàn),包括required、mask(匹配正則表達(dá)式)、最小長(zhǎng)度、最大長(zhǎng)度、范圍、日期等

              .xml配置文件:主要包括兩個(gè)配置文件,一個(gè)是validator-rules.xml,另一個(gè)是validation.xml。前者的內(nèi)容主要包含一些校驗(yàn)規(guī)則,后者則包含需要校驗(yàn)的一些form及其組件的集合。

              資源綁定:提供(本地化)標(biāo)簽和消息,缺省地共享struts的資源綁定。即校驗(yàn)所用到的一些標(biāo)簽與消息都寫在ApplicationResources.properity文件中。

              Jsp tag:為給定的form或者action path生成JavaScript validations。

              ValidatorForm:它是ActionForm的一個(gè)子類。

              為了對(duì)Validator框架有一個(gè)比較直觀的認(rèn)識(shí),我們還是以前面的登陸例子的輸入來(lái)示范一下Validator框架的使用過程:

              首先,找一個(gè)validator-rules.xml文件放在mystruts\WEB-INF目錄下,下面是該文件中涉及到的required驗(yàn)證部分代碼的清單:

          <validator name="required"
          <!--①-->
                      classname="org.apache.struts.validator.FieldChecks"
                         method="validateRequired"
                   methodParams="java.lang.Object,
                                 org.apache.commons.validator.ValidatorAction,
                                 org.apache.commons.validator.Field,
                                 org.apache.struts.action.ActionErrors,
          javax.servlet.http.HttpServletRequest"
          <!--②-->
                            msg="errors.required">
          <!--③-->
                   <javascript><![CDATA[
                      function validateRequired(form) {
                          var isValid = true;
                          var focusField = null;
                          var i = 0;
                          var fields = new Array();
                          oRequired = new required();
                          for (x in oRequired) {
                                var field = form[oRequired[x][0]];
                                
                              if (field.type == 'text' ||
                                  field.type == 'textarea' ||
                                  field.type == 'file' ||
                                  field.type == 'select-one' ||
                                  field.type == 'radio' ||
                                  field.type == 'password') {
                                  
                                  var value = '';
                                  // get field's value
                                  if (field.type == "select-one") {
                                  var si = field.selectedIndex;
                                  if (si >= 0) {
                                  value = field.options[si].value;
                                                    }
                                              } else {
                                                    value = field.value;
                                              }
                                  
                                  if (trim(value).length == 0) {
                                  
                                        if (i == 0) {
                                            focusField = field;
                                        }
                                        fields[i++] = oRequired[x][1];
                                        isValid = false;
                                  }
                              }
                          }
                          if (fields.length > 0) {
                             focusField.focus();
                             alert(fields.join('\n'));
                          }
                          return isValid;
                      }
                      
                      // Trim whitespace from left and right sides of s.
                      function trim(s) {
                          return s.replace( /^\s*/, "" ).replace( /\s*$/, "" );
                      }
                      
                      ]]>
                   </javascript>
          
          </validator>



              ① 節(jié)的代碼是引用一個(gè)服務(wù)器邊的驗(yàn)證器,其對(duì)應(yīng)的代碼清單如下:

          public static boolean validateRequired(Object bean,
                                                     ValidatorAction va, Field field,
                                                     ActionErrors errors,
                                                     HttpServletRequest request) {
          
                  String value = null;
                  if (isString(bean)) {
                      value = (String) bean;
                  } else {
                      value = ValidatorUtil.getValueAsString(bean, field.getProperty());
                  }
                  
                  if (GenericValidator.isBlankOrNull(value)) {
                      errors.add(field.getKey(), Resources.getActionError(request, va, field));
                      return false;
                  } else {
                      return true;
                  }
          }



              ② 節(jié)是驗(yàn)證失敗后的出錯(cuò)信息,要將對(duì)應(yīng)這些鍵值的信息寫入到ApplicationResources.properity文件中,常見的錯(cuò)誤信息如下:

          # Standard error messages for validator framework checks
          errors.required={0} is required.
          errors.minlength={0} can not be less than {1} characters.
          errors.maxlength={0} can not be greater than {1} characters.
          errors.invalid={0} is invalid.
          errors.byte={0} must be a byte.
          errors.short={0} must be a short.
          errors.integer={0} must be an integer.
          errors.long={0} must be a long.
          errors.float={0} must be a float.
          errors.double={0} must be a double.
          errors.date={0} is not a date.
          errors.range={0} is not in the range {1} through {2}.
          errors.creditcard={0} is an invalid credit card number.
          errors.email={0} is an invalid e-mail address.



              ③ 節(jié)的代碼用于客戶邊的JavaScript驗(yàn)證

          其次,在validation.xml文件中配置要驗(yàn)證的form極其相應(yīng)的字段,下面是該文件中的代碼:

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation
          //DTD Commons Validator Rules Configuration 1.0//EN" 
          "http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">
          <form-validation>
          <formset>
          <form name="userInfoForm">
          <field property="username"
          depends="required,mask,minlength,maxlength">
          <arg0 key="logon.jsp.prompt.username" resource="true"/>
          <arg1 name="minlength" key="${var:minlength}" resource="false"/>
          <arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
          <var>
          <var-name>mask</var-name>
          <var-value>^\w</var-value>
          </var>
          <var>
          <var-name>minlength</var-name>
          <var-value>2</var-value>
          </var>
          <var>
          <var-name>maxlength</var-name>
          <var-value>16</var-value>
          </var>
          </field>
          <field property="password"
          depends="required,minlength,maxlength">
          <arg0 key="logon.jsp.prompt.password" resource="true"/>
          <arg1 name="minlength" key="${var:minlength}" resource="false"/>
          <arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
          <var>
          <var-name>minlength</var-name>
          <var-value>2</var-value>
          </var>
          <var>
          <var-name>maxlength</var-name>
          <var-value>16</var-value>
          </var>
          </field>
          </form>
          </formset>
          </form-validation>



              這里要注意的是:該文中的中的鍵值都是取自資源綁定中的。前面還講到了出錯(cuò)信息也是寫入ApplicationResources.properity文件中,因此,這就為國(guó)際化提供了一個(gè)很好的基礎(chǔ)。

              再次,為了使服務(wù)器邊的驗(yàn)證能夠進(jìn)行,將用到的formBean從ActionForm的子類改為ValidatorForm的子類,即:
              將public class UserInfoForm extends ActionForm改為:public class UserInfoForm extends ValidatorForm

              到此,進(jìn)行服務(wù)器邊的驗(yàn)證工作已經(jīng)一切準(zhǔn)備得差不多了,此時(shí),只要完成最后步驟就可以實(shí)驗(yàn)服務(wù)器邊的驗(yàn)證了。但大多數(shù)情況下,人們總希望把這些基本的簡(jiǎn)單驗(yàn)證放在客戶邊進(jìn)行。

              為了能進(jìn)行客戶邊的驗(yàn)證,我們還要對(duì)logon.jsp文件做適當(dāng)?shù)男薷摹?

              將

          <html:form action="/logonAction.do" focus="username">

          改為
              

          <html:form action="/logonAction.do" focus="username" onsubmit="return validateUserInfoForm(this)">



              在標(biāo)簽后加上:
              

          <html:javascript dynamicJavascript="true" staticJavascript="true" formName="userInfoForm"/>



              最后,對(duì)struts的配置文件struts-config.xml作適當(dāng)?shù)男薷模?
              1、將

          <action input="/logon.jsp" name="userInfoForm"
           path="/logonAction" scope="session" type="action.LogonAction" validate="false" >

          改為

          <action input="/logon.jsp" name="userInfoForm" 
          path="/logonAction" scope="session" type="action.LogonAction" validate="true" >

          其作用是要求進(jìn)行校驗(yàn)

              2、將下列代碼放在struts-config.xml文件中的標(biāo)簽前。其作用是將用于校驗(yàn)的各個(gè)組件結(jié)合在一起。

          <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
              <set-property property="pathnames"
                value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml" />
          </plug-in>



              到此為止,我們的一切工作準(zhǔn)備就緒,您可以享受自己的勞動(dòng)成果了,試著輸入各種組合的用戶名和口令,看看它們的驗(yàn)證效果。仔細(xì)體會(huì)你會(huì)發(fā)現(xiàn),服務(wù)器邊的驗(yàn)證要更全面一些,比如對(duì)password的字符長(zhǎng)度的驗(yàn)證。

              參考文獻(xiàn):
          《Struts in Action》Ted Husted Cedric Dumoulin George Franciscus David Winterfeldt著
          《Programming Jakarta Struts》Chuck Cavaness著

          第7部分

          上一篇文章中介紹校驗(yàn)時(shí)提到客戶邊的校驗(yàn)用到了JavaScript,實(shí)際上用Struts配合JavaScript還可以實(shí)現(xiàn)許多有用的功能,比如,級(jí)聯(lián)下拉菜單的實(shí)現(xiàn)就是一個(gè)典型的例子:

          本例假設(shè)要實(shí)現(xiàn)的是一個(gè)文章發(fā)布系統(tǒng),我們要發(fā)布的文章分為新聞?lì)惡图夹g(shù)類,其中新聞?lì)愑址譃闀r(shí)事新聞和行業(yè)動(dòng)態(tài);技術(shù)類又分為操作系統(tǒng)、數(shù)據(jù)庫(kù)、和編程語(yǔ)言等,為了便于添加新的條目,所有這些都保存在數(shù)據(jù)庫(kù)表中。

          為此,我們建立一個(gè)名為articleClass的表和一個(gè)名為articleSubClass的表。

          articleClass表的結(jié)構(gòu)如下:
          articleClassID字段:char類型,長(zhǎng)度為2,主鍵
          articleClassName字段:varchar類型,長(zhǎng)度為20
          articleSubClass表的結(jié)構(gòu)如下:
          articleClassID字段:char類型,長(zhǎng)度為2
          articleSubClassID字段:char類型,長(zhǎng)度為2與articleClassID一起構(gòu)成主鍵
          articleSubClassName字段:varchar類型,長(zhǎng)度為20


          表建好后,在articleClass表中錄入如下數(shù)據(jù):如,01、新聞?lì)悾?2、技術(shù)類

          在articleSubClass表中錄入:01、01、時(shí)事新聞;01、02、行業(yè)動(dòng)態(tài);02、01、操作系統(tǒng)等記錄。到這里,數(shù)據(jù)庫(kù)方面的準(zhǔn)備工作就已做好。

          有了前面做登錄例子的基礎(chǔ),理解下面要進(jìn)行的工作就沒有什么難點(diǎn)了,我們現(xiàn)在的工作也在原來(lái)mystruts項(xiàng)目中進(jìn)行。首先,建立需要用到的formbean即ArticleClassForm,其代碼如下:

          package entity;
          import org.apache.struts.action.*;
          import javax.servlet.http.*;
          import java.util.Collection;
          
          public class ArticleClassForm extends ActionForm {
            //為select的option做準(zhǔn)備
            private Collection beanCollection;
            private String singleSelect = "";
            private String[] beanCollectionSelect = { "" };
            private String articleClassID;
            private String articleClassName;
            private String subI;//子類所在行數(shù)
            private String subJ;//子類所在列數(shù)
            private String articleSubClassID;
            private String articleSubClassName;
          
            public Collection getBeanCollection(){
              return beanCollection;
            }
          
            public void setBeanCollection(Collection beanCollection){
              this.beanCollection=beanCollection;
            }
          
            public String getSingleSelect() {
              return (this.singleSelect);
            }
            public void setSingleSelect(String singleSelect) {
              this.singleSelect = singleSelect;
            }
            public String[] getBeanCollectionSelect() {
              return (this.beanCollectionSelect);
            }
            public void setBeanCollectionSelect(String beanCollectionSelect[]) {
              this.beanCollectionSelect = beanCollectionSelect;
            }
          
            public String getArticleClassID() {
              return articleClassID;
            }
            public void setArticleClassID(String articleClassID) {
              this.articleClassID = articleClassID;
            }
            public String getArticleClassName() {
              return articleClassName;
            }
            public void setArticleClassName(String articleClassName) {
              this.articleClassName = articleClassName;
            }
          
            public String getSubI() {
              return subI;
            }
            public void setSubI(String subI) {
              this.subI = subI;
            }
          
            public String getSubJ() {
              return subJ;
            }
            public void setSubJ(String subJ) {
              this.subJ = subJ;
            }
          
            public String getArticleSubClassID() {
              return articleSubClassID;
            }
            public void setArticleSubClassID(String articleSubClassID) {
              this.articleSubClassID = articleSubClassID;
            }
          
            public String getArticleSubClassName() {
              return articleSubClassName;
            }
            public void setArticleSubClassName(String articleSubClassName) {
              this.articleSubClassName = articleSubClassName;
            }
          }


          將它放在包entity中。其次,我們的系統(tǒng)要訪問數(shù)據(jù)庫(kù),因此也要建立相應(yīng)的數(shù)據(jù)庫(kù)訪問對(duì)象ArticleClassDao,其代碼如下:

          package db;
          
          import entity.ArticleClassForm;
          import db.*;
          import java.sql.*;
          
          import java.util.Collection;
          import java.util.ArrayList;
          import org.apache.struts.util.LabelValueBean;
          public class ArticleClassDao {
            private Connection con;
          
            public ArticleClassDao(Connection con) {
              this.con=con;
            }
            public Collection findInUseForSelect(){
              PreparedStatement ps=null;
              ResultSet rs=null;
              ArrayList list=new ArrayList();
              String sql="select * from articleClass order by articleClassID";
              try{
                if(con.isClosed()){
                  throw new IllegalStateException("error.unexpected");
                }
                ps=con.prepareStatement(sql);
                rs=ps.executeQuery();
          
                while(rs.next()){
                  String value=rs.getString("articleClassID");
                  String label=rs.getString("articleClassName");
                  list.add(new LabelValueBean(label,value));
                }
                return list;
              }
              catch(SQLException e){
                e.printStackTrace();
                throw new RuntimeException("error.unexpected");
              }
              finally{
                try{
                  if(ps!=null)
                    ps.close();
                  if(rs!=null)
                    rs.close();
          
                }
                catch(SQLException e){
                  e.printStackTrace();
                  throw new RuntimeException("error.unexpected");
                }
              }
            }
          
            public Collection findInUseForSubSelect(){
              PreparedStatement ps=null;
              ResultSet rs=null;
              PreparedStatement psSub=null;
              ResultSet rsSub=null;
              int i=0;//大類記數(shù)器
              int j=0;//小類記數(shù)器
              String classID="";
              String subClassID="";
              String subClassName="";
          
              ArrayList list=new ArrayList();
              ArticleClassForm articleClassForm;
          
          
              String sql="select * from articleClass order by articleClassID";
              try{
                if(con.isClosed()){
                  throw new IllegalStateException("error.unexpected");
                }
                ps=con.prepareStatement(sql);
                rs=ps.executeQuery();
          
                while(rs.next()){
                  i++;
                  classID=rs.getString("articleClassID");
                  String sqlSub="select * from articleSubClass where articleClassID=? 
                      order by articleSubClassID";
                  psSub=con.prepareStatement(sqlSub);
                  psSub.setString(1,classID);
                  rsSub=psSub.executeQuery();
          
                  articleClassForm=new ArticleClassForm();
                  articleClassForm.setSubI(""+i);
                  articleClassForm.setSubJ(""+j);
                  articleClassForm.setArticleSubClassID("請(qǐng)輸入一個(gè)小類");
                  articleClassForm.setArticleSubClassName("請(qǐng)輸入一個(gè)小類");
                  list.add(articleClassForm);
          
                  while(rsSub.next()){
                    subClassID=rsSub.getString("articleSubClassID");
                    subClassName=rsSub.getString("articleSubClassName");
                    j++;
                    //optionStr="articleSubClassGroup[" + i + "][" + j + "]=
          new Option('"+ subClassName +"','"+ subClassID+ "')";
                    articleClassForm=new ArticleClassForm();
                    articleClassForm.setSubI(""+i);
                    articleClassForm.setSubJ(""+j);
                    articleClassForm.setArticleSubClassID(subClassID);
                    articleClassForm.setArticleSubClassName(subClassName);
                    list.add(articleClassForm);
                  }
          
                  j=0;
                }
                return list;
              }
              catch(SQLException e){
                e.printStackTrace();
                throw new RuntimeException("error.unexpected");
              }
              finally{
                try{
                  if(ps!=null)
                    ps.close();
                  if(rs!=null)
                    rs.close();
          
                }
                catch(SQLException e){
                  e.printStackTrace();
                  throw new RuntimeException("error.unexpected");
                }
              }
            }
          }


          將它保存在db目錄中。它們的目的是將文章的類和子類信息從數(shù)據(jù)庫(kù)表中讀出,以一定的格式保存在集合對(duì)象中以供頁(yè)面顯示。

          再次,我們要建立相應(yīng)的jsp文件,文件名為selectArticleClass.jsp,代碼如下:

          <%@ page contentType="text/html; charset=UTF-8" %>
          <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
          <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
          <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
          <html>
          <head>
          <title>
          選擇文件類別
          </title>
          </head>
          <body bgcolor="#ffffff">
          <h3>
          選擇文件所屬類型
          </h3>
          <html:errors/>
          <table width="500" border="0" cellspacing="0" cellpadding="0">
            <tr>
              <td><html:form name="articleClassForm" type="entity.ArticleClassForm"
                action="selectArticleClassAction.do">
                  <table width="500" border="0" cellspacing="0" cellpadding="0">
                    <tr>
                      <td align="right">文章大類*</td>
                      <td>
                        <html:select property="articleClassID" styleClass="word"
                             onchange="articleClassFormredirect(this.options.selectedIndex)">
                          <html:option value="">請(qǐng)選擇一個(gè)大類</html:option>
                          <html:optionsCollection name="articleClassForm" property="beanCollection" styleClass="word"/>
                        </html:select>
                      </td>
                    </tr>
                    <tr>
                      <td align="right">文章小類*</td>
                      <td>
                        <select name="articleSubClassID" Class="word" >
                          <option value="">請(qǐng)選擇一個(gè)小類</option>
                        </select>
                        <SCRIPT language=JavaScript>
                        <!--
                        var articleSubClassGroups=document.articleClassForm.articleClassID.
                             options.length
                        var articleSubClassGroup=new Array(articleSubClassGroups)
                        for (i=0; i<articleSubClassGroups; i++)
                        articleSubClassGroup[i]=new Array()
                        <logic:iterate name="articleSubClassList" id="articleClassForm"
                              scope="request" type="entity.ArticleClassForm">
                          articleSubClassGroup[<bean:write name="articleClassForm"
                                  property="subI"/>][<bean:write name="articleClassForm"
                                  property="subJ"/>]=new Option("<bean:write name="articleClassForm"
                            property="articleSubClassName"/>","<bean:write name="articleClassForm"
                            property="articleSubClassID"/>")
                        </logic:iterate>
                        var articleSubClassTemp=document.articleClassForm.articleSubClassID
                        function articleClassFormredirect(x){
                          for (m=articleSubClassTemp.options.length-1;m>0;m--)
                          articleSubClassTemp.options[m]=null
                          for (i=0;i<articleSubClassGroup[x].length;i++){
                            articleSubClassTemp.options[i]=new
                      Option(articleSubClassGroup[x][i].text,
                             articleSubClassGroup[x][i].value)
                          }
                          articleSubClassTemp.options[0].selected=true
                        }
                       //-->
                     </SCRIPT>
                      </td>
                    </tr>
                  </table>
                </html:form>
              </td>
            </tr>
          </table>
          </body>
          </html>


          這里值得重點(diǎn)關(guān)注的是其中的JavaScript代碼,有興趣的可以仔細(xì)分析一下它們是怎樣配合集合中的元素來(lái)實(shí)現(xiàn)級(jí)聯(lián)選擇的。

          最后,為了例子的完整。我們將涉及到action代碼和必要的配置代碼在下面列出:其中,action的文件名為SelectArticleClassAction.java,代碼如下:

          package action;
          import entity.*;
          import org.apache.struts.action.*;
          import javax.servlet.http.*;
          import javax.sql.DataSource;
          import java.sql.Connection;
          import db.ArticleClassDao;
          import java.util.Collection;
          import java.sql.SQLException;
          public class SelectArticleClassAction extends Action {
            public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm,
            HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
              /**@todo: complete the business logic here, this is just a skeleton.*/
              ArticleClassForm articleClassForm = (ArticleClassForm) actionForm;
              DataSource dataSource;
              Connection cnn=null;
              ActionErrors errors=new ActionErrors();
              try{
                dataSource = getDataSource(httpServletRequest,"A");
                cnn = dataSource.getConnection();
                ArticleClassDao articleClassDao=new ArticleClassDao(cnn);
          
                Collection col=articleClassDao.findInUseForSelect();
                articleClassForm.setBeanCollection(col);
                httpServletRequest.setAttribute("articleClassList",col);
          
                //處理子類選項(xiàng)
                Collection subCol=articleClassDao.findInUseForSubSelect();
                httpServletRequest.setAttribute("articleSubClassList",subCol);
                return actionMapping.findForward("success");
              }
              catch(Throwable e){
                e.printStackTrace();
                //throw new RuntimeException("未能與數(shù)據(jù)庫(kù)連接");
                ActionError error=new ActionError(e.getMessage());
                errors.add(ActionErrors.GLOBAL_ERROR,error);
              }
              finally{
                try{
                  if(cnn!=null)
                    cnn.close();
                }
                catch(SQLException e){
                  throw new RuntimeException(e.getMessage());
                }
              }
              saveErrors(httpServletRequest,errors);
              return actionMapping.findForward("fail");
            }
          }


          將其保存在action目錄中。

          在struts-config.xml文件中做如下配置:

          <form-beans>
          中加入
          <form-bean name="articleClassForm" type="entity.ArticleClassForm" />


          ><action-mappings>
          中加入:

          <action name="articleClassForm" path="/selectArticleClassAction" scope="session"
                type="action.SelectArticleClassAction" validate="false">
          <forward name="success" path="/selectArticleClass.jsp" />
          <forward name="fail" path="/genericError.jsp" />
          </action>


          為了對(duì)應(yīng)配置中的
          <forward name="fail" path="/genericError.jsp" />
          ,我們還要提供一個(gè)顯示錯(cuò)誤信息的jsp頁(yè)面,其代碼如下:

          <%@ page contentType="text/html; charset=UTF-8" %>
          <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
          <html>
          <head>
          <title>
          genericError
          </title>
          <link href="css/mycss.css" rel="stylesheet" type="text/css">
          </head>
          <body bgcolor="#ffffff">
          <html:errors/>
          </body>
          </html>


          現(xiàn)在一切就緒,可以編譯執(zhí)行了。在瀏覽器中輸入:http://127.0.0.1:8080/mystruts/selectArticleClassAction.do就可以看到該例子的運(yùn)行結(jié)果了

        16. posted on 2005-10-24 22:15 Sung 閱讀(1108) 評(píng)論(0)  編輯  收藏 所屬分類: Struts
          主站蜘蛛池模板: 原平市| 南平市| 京山县| 泾川县| 建德市| 牡丹江市| 乌拉特前旗| 日喀则市| 成都市| 德庆县| 武乡县| 沙田区| 漯河市| 广河县| 武穴市| 泰来县| 泾阳县| 原阳县| 府谷县| 如东县| 阳江市| 通州市| 新化县| 巴南区| 黄大仙区| 宁城县| 华安县| 郓城县| 祥云县| 大化| 宜兴市| 葫芦岛市| 安义县| 社旗县| 婺源县| 玉田县| 青海省| 卫辉市| 巴彦县| 吉木乃县| 汉阴县|