<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 495,comments - 227,trackbacks - 0
    (第1部分)

    一、 什么是Struts

    框架(Framework)是可重用的,半完成的應用程序,可以用來產生專門的定制程序。

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

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

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

    Model 1結構如圖1所示:



    圖1

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

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

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



    圖2

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

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



    圖三

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

    過程如圖四所示:



    圖四

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

  • 校驗前提條件或者聲明
  • 調用需要的業(yè)務邏輯方法
  • 檢測或處理其他錯誤
  • 路由控制到相關視圖

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

    二、為什么要使用Struts框架

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

    三、Struts的安裝與基本配置

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

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

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

    truts.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)建一個web.xml文件,這是一個基于servlet的Web應用程序都需要的部署描述文件,一個Struts Web應用,在本質上也是一個基于servlet的Web應用,它也不能例外。

    Struts有兩個組件要在該文件中進行配置,它們是:ActionServlet和標簽庫。下面是一個配置清單:

    lt;?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>


    上面我們在web.xml中完成了對servlet和標簽庫的基本配置,而更多的框架組件要在struts-config.xml中進行配置:

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

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


    配置清單如下:

    lt;?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>


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

    下面,我們就從一個最簡單的登錄例子入手,以對Struts的主要部分有一些直觀而清晰的認識。這個例子功能非常簡單,假設有一個名為lhb的用戶,其密碼是awave,程序要完成的任務是,呈現(xiàn)一個登錄界面給用戶,如果用戶輸入的名稱和密碼都正確返回一個歡迎頁面給用戶,否則,就返回登錄頁面要求用戶重新登錄并顯示相應的出錯信息。這個例子在我們講述Struts的基礎部分時會反復用到。之所以選用這個簡單的程序作為例子是因為不想讓過于復雜的業(yè)務邏輯來沖淡我們的主題。

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

    (第2部分)

    (第三部分)

    一、JDBC的工作原理

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

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

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

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

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

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

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

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

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

    首先,我們討論漢字變成問號的問題。

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

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

    a_gbk.jsp的代碼如下:

    /FONT>

    參考文獻:

    UTF-8 and Unicode FAQ

    《JSP動態(tài)網站技術入門與提高》太陽工作室 孫曉龍 趙莉編著

    第5部分

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

    Struts主要采用兩個i18n組件來實現(xiàn)國際化編程:

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

    用Struts實現(xiàn)國際化編程的標準做法是:生成一個java屬性文件集。每個文件包含您的應用程序要顯示的所有消息的鍵/值對。

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

    ApplicationResources.properties文件的鍵/值都是英文的,而其他語種文件的鍵是英文的,值則是對應的語言。如在我們前面的登錄例子中的鍵/值對:logon.jsp.prompt.username=Username:在中文文件中就是:logon.jsp.prompt.username=用戶名:當然,在實際應用時要把中文轉換為AscII碼。

    有了上一篇文章和以上介紹的一些基礎知識后。我們就可以將我們的登錄程序進行國際化編程了。

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

    ,在我們的登錄例子中已經這樣做了,這里不需要再改動。

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

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

    /FONT>

    lt;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é)的代碼是引用一個服務器邊的驗證器,其對應的代碼清單如下:

    ublic 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é)是驗證失敗后的出錯信息,要將對應這些鍵值的信息寫入到ApplicationResources.properity文件中,常見的錯誤信息如下:

     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驗證

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

    lt;?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>



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

    ????再次,為了使服務器邊的驗證能夠進行,將用到的formBean從ActionForm的子類改為ValidatorForm的子類,即:
    ????將public class UserInfoForm extends ActionForm改為:public class UserInfoForm extends ValidatorForm

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

    ????為了能進行客戶邊的驗證,我們還要對logon.jsp文件做適當?shù)男薷摹?

    ????將

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

    改為
    ????

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



    ????在標簽后加上:
    ????

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



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

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

    改為

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

    其作用是要求進行校驗

    ????2、將下列代碼放在struts-config.xml文件中的標簽前。其作用是將用于校驗的各個組件結合在一起。

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



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

    ????參考文獻:
    《Struts in Action》Ted Husted Cedric Dumoulin George Franciscus David Winterfeldt著
    《Programming Jakarta Struts》Chuck Cavaness著

    第7部分

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

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

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

    rticleClass表的結構如下:
    articleClassID字段:char類型,長度為2,主鍵
    articleClassName字段:varchar類型,長度為20
    articleSubClass表的結構如下:
    articleClassID字段:char類型,長度為2
    articleSubClassID字段:char類型,長度為2與articleClassID一起構成主鍵
    articleSubClassName字段:varchar類型,長度為20


    表建好后,在articleClass表中錄入如下數(shù)據(jù):如,01、新聞類;02、技術類

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

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

    ackage entity;
    import org.apache.struts.action.*;
    import javax.servlet.http.*;
    import java.util.Collection;
    
    public class ArticleClassForm extends ActionForm {
      //為select的option做準備
      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ù)庫,因此也要建立相應的數(shù)據(jù)庫訪問對象ArticleClassDao,其代碼如下:

    ackage 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("請輸入一個小類");
            articleClassForm.setArticleSubClassName("請輸入一個小類");
            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ù)庫表中讀出,以一定的格式保存在集合對象中以供頁面顯示。

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

    lt;%@ 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="">請選擇一個大類</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="">請選擇一個小類</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>


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

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

    ackage 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);
    
          //處理子類選項
          Collection subCol=articleClassDao.findInUseForSubSelect();
          httpServletRequest.setAttribute("articleSubClassList",subCol);
          return actionMapping.findForward("success");
        }
        catch(Throwable e){
          e.printStackTrace();
          //throw new RuntimeException("未能與數(shù)據(jù)庫連接");
          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文件中做如下配置:

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


    gt;<action-mappings>
    中加入:

    lt;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>


    為了對應配置中的
    lt;forward name="fail" path="/genericError.jsp" />
    ,我們還要提供一個顯示錯誤信息的jsp頁面,其代碼如下:

    lt;%@ 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就可以看到該例子的運行結果了。(T111)

    ackage 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);是一個關鍵句子。

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

    lt;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>



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

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

    Application Resource for the logon.jsp
    logon.jsp.title=登錄頁
    logon.jsp.page.heading=歡迎 世界!
    logon.jsp.prompt.username=用戶名:
    logon.jsp.prompt.password=口令:
    logon.jsp.prompt.submit=提交
    logon.jsp.prompt.reset=復位
    
    #Application Resource for the main.jsp
    main.jsp.title=主頁
    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">用戶名/口令是無效的</font></li>
    error.removed.user=<li><font color="red">找不到該用戶</font></li>
    error.unexpected=<li><font color="red">不可預期的錯誤</font></li>



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

    具體做法是打開一個dos窗口,到mystruts\WEB-INF\classes目錄下,運行如下語句:

    native2ascii -encoding GBK ApplicationResources_zh.properties ApplicationResources_zh_CN.properties

    生成的文件ApplicationResources_zh_CN.properties的內容如下:

    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>



    從這里可以看出,所有的中文字都轉換成了對應的Unicode碼。

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

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

    參考文獻:
    《Programming Jakarta Struts》Chuck Cavaness著
    《Mastering Jakarta Struts》James Goodwill著

    第6部分

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

    ????Validator框架大致有如下幾個主要組件:

    ????Validators:是Validator框架調用的一個Java類,它處理那些基本的通用的校驗,包括required、mask(匹配正則表達式)、最小長度、最大長度、范圍、日期等

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

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

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

    ????ValidatorForm:它是ActionForm的一個子類。

    ????為了對Validator框架有一個比較直觀的認識,我們還是以前面的登陸例子的輸入來示范一下Validator框架的使用過程:

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

    lt;%@ 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的代碼如下:

    lt;%@ 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并輸入一個"你"字,點擊OK按鈕提交表單,則會出現(xiàn)如圖1所示的結果:



    圖1

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

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

    String(byte[] bytes,String encoding);

    在國標平臺上,該函數(shù)會認為bytes是按GBK編碼的,如果后一個參數(shù)省略,它也會認為是encoding是GBK。

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

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

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

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

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

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

    a.jsp代碼:

    lt;%@ 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中輸入"你"字,你會發(fā)現(xiàn)顯示結果中,一個漢字是用三個byte表示的,它們的值分別是0xe4、0xbd、0xa0,也就是說用UTF-8來表示漢字,每個漢字要比GBK多占用一個byte,這也是使用UTF-8要多付出的一點代價吧。

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

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

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

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

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


    下面,我們就一步步按照上面所說的步驟來完成我們的應用程序:

    第一步,我們的應用程序的Views部分包含兩個.jsp頁面:一個是登錄頁面logon.jsp,另一個是用戶登錄成功后的用戶功能頁main.jsp,暫時這個頁面只是個簡單的歡迎頁面。

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

    lt;%@ 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的代碼清單如下:

    lt;%@ 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文件,會發(fā)現(xiàn)它有這么兩個鮮明的特點:一是文件頭部有諸如:



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

  • bean標簽,作用是在jsp中操縱bean
  • logic標簽,作用是在jsp中進行流程控制
  • html標簽,作用是顯示表單等組件
  • template標簽,作用是生成動態(tài)模板

    關于每類標簽的具體作用及語法,因受篇幅限制,不在這里詳細討論,大家可參考struts手冊之類的資料。只是心里要明白所謂標簽其后面的東西就是一些類,這點與bean有些相似,它們在后端運行,生成標準的html標簽返回給瀏覽器。

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

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

    這個簡單的應用所用到的ActionForm為UserInfoForm,代碼清單如下:

    ackage 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;
      }
    }


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

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

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

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


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

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

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

    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:


    到此,我們已完成了第二個步驟。

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

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

    ackage 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();
        //聲明錯誤集對象
        ActionErrors errors = new ActionErrors();
        //校驗輸入
        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);
        }
        
        //調用業(yè)務邏輯
        if(errors.size()==0){
          String validated = "";
          try{
            UserInfoBo userInfoBo=new UserInfoBo();
            validated =userInfoBo.validatePwd(username,password);
            if(validated.equals("match")){
              //一切正常就保存用戶信息并轉向成功的頁面??????
              HttpSession session = request.getSession();
              session.setAttribute("userInfoForm", form);          
          ??????  return mapping.findForward("success");
            } 
          }
          
          catch(Throwable e){
            //處理可能出現(xiàn)的錯誤
            e.printStackTrace();
            ActionError error=new ActionError(e.getMessage());
            errors.add(ActionErrors.GLOBAL_ERROR,error);
          }
        }  
        //如出錯就轉向輸入頁面,并顯示相應的錯誤信息
        saveErrors(request, errors);    
        return new ActionForward(mapping.getInput());    
      } 
    }


    這個action類中有兩個錯誤消息鍵要加到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的關系,也就是配置所謂的ActionMapping。它們在struts-config.xml中的位置是排在 標簽后,我們的登錄程序的配置清單如下:

    lt;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>


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

    ackage 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包中。

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

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


    到此為止,我們已經完成了這個簡單登錄程序的所有組件。下面就可以享受我們的勞動成果了。

    第六步、編譯運行應用程序。

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

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


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



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

    上面雖然是一個功能很簡單的應用程序,但麻雀雖小,五臟俱全,基本涉及到了struts的主要組成部分。下面我們就來分析一下程序的特點和基本的工作原理。

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

    大家一定注意到了,在本例的業(yè)務邏輯模塊UserInfoBo中,將用戶與密碼是寫死在程序中的,在一個真實的應用程序中是不會這樣做的,那些需要永久保存的信息如,username及口令等都會保存在數(shù)據(jù)庫文件之類的永久介質中,下一篇文章我們將介紹在struts中如何訪問數(shù)據(jù)庫
  • 建立一個與數(shù)據(jù)源的連接
  • 發(fā)送SQL語句到數(shù)據(jù)源
  • 取回結果集

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

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

    這里,是講的JDBC的基本工作過程,實際應用中,往往會使用JDBC擴展對象如DataSource等,限于篇幅,就不在此詳細討論了。

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

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

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

    這里,有一點要引起大家的注意的,就是,struts-config.xml中配置的各個項目是有一定的順序要求的,幾個主要項目的順序大致是這樣的:

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


    在配置時要遵守上述順序

    lt;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>


    我們來對這段配置代碼做一個簡單的說明:

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

    lt;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ù)庫服務器名(本例是用代表本機的ip地址)和數(shù)據(jù)庫名稱要與您的具體情況相同。同時,還要注意訪問數(shù)據(jù)庫的用戶名和口令也要合乎您的實際情況。

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

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

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

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

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

    ackage 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();
        //聲明錯誤集對象
        ActionErrors errors = new ActionErrors();
        //聲明數(shù)據(jù)源和連接對象
        DataSource dataSource;
        Connection cnn=null;
    
        //校驗輸入
        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);
        }
    
        //調用業(yè)務邏輯
        if(errors.size()==0){
          String validated = "";
          try{
            //取得數(shù)據(jù)庫連接
            dataSource = getDataSource(request,"A");
            cnn = dataSource.getConnection();
    
            UserInfoBo userInfoBo=new UserInfoBo(cnn);
            validated =userInfoBo.validatePwd(username,password);
            if(validated.equals("match")){
              //一切正常就保存用戶信息并轉向成功的頁面
              HttpSession session = request.getSession();
              session.setAttribute("userInfoForm", form);
                    return mapping.findForward("success");
            }
          }
    
          catch(Throwable e){
            //處理可能出現(xiàn)的錯誤
            e.printStackTrace();
            ActionError error=new ActionError(e.getMessage());
            errors.add(ActionErrors.GLOBAL_ERROR,error);
          }
        }
        //如出錯就轉向輸入頁面,并顯示相應的錯誤信息
        saveErrors(request, errors);
        return new ActionForward(mapping.getInput());
      }
    }


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

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

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

    ackage 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")){
            //如果用戶名與口令不匹配則報此錯
            throw new RuntimeException("error.logon.invalid"); 
          }
          else if(validateResult.equals("error.removed.user")){
            //如果找不到用戶則報此錯,這樣用戶看到的出錯信息會更詳細
            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;
      }
    }


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

    ackage 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");
          }
        }
      }
    }


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

    要訪問數(shù)據(jù)庫,一般要經歷的如下幾個步驟:
  • 獲得到數(shù)據(jù)庫的連接
  • 創(chuàng)建SQL語句
  • 執(zhí)行SQL語句
  • 管理結果集

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

    Action在調用業(yè)務對象時將連接作為一個參數(shù)傳給業(yè)務對象,再由業(yè)務對象傳給數(shù)據(jù)庫訪問對象。

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

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

    細心一點的讀者可能都注意到了,到目前為止,我們程序中的各種消息都不是用中文表示的,在下一篇文章中,我們將討論Struts的國際化編程即所謂的i18n編程,對我們在編程中經常遇到的亂碼問題也一同作些分析。

    參考文獻:
    《JSP Web 編程指南》---電子工業(yè)出版社 Jayson Falkner等著 司光亞 牛紅等譯
    《Java數(shù)據(jù)庫編程寶典》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部分)

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

    一、從ASCII到Unicode(UTF-8)

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

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

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

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

    我國的臺灣地區(qū)使用的文字是繁體字,其字符集是BIG5,而日本采用的字符集則是SJIS。它們的編碼方法與GB2312類似,它們的ASCII字符部分是兼容的,但擴展部分的編碼則是不兼容的,比如這幾種字符集中都有"中文"這兩個字符,但他們在各自的字符集中的編碼并不相同,這就是用GB2312寫成的網頁用BIG5瀏覽時,看到的是亂糟糟的信息的原因。

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

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

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

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

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

    有了Unicode,字符集問題接近了完美的解決,但不要高興得過早。由于歷史的原因:一些操作系統(tǒng)如:Unix、Linux等都是基于ASCII設計的。此外,還有一些數(shù)據(jù)庫管理系統(tǒng)軟件如:Oracle等也是圍繞ASCII來設計的(從其8i的白皮書上介紹的設置系統(tǒng)字符集和字段的字符集中可以間接地看到這一點)。在這些系統(tǒng)中直接用Unicode會導致嚴重的問題。用這些編碼的字符串會包含一些特殊的字符, 比如 '\0' 或 '/', 它們在 文件名和其他 C 庫函數(shù)參數(shù)里都有特別的含義。 另外, 大多數(shù)使用 ASCII 文件的 UNIX 下的工具, 如果不進行重大修改是無法讀取 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 字符被編碼為一個多個字節(jié)的串, 每個字節(jié)都有標記位集。 因此,ASCII 字節(jié) (0x00-0x7F) 不可能作為任何其他字符的一部分。

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

  • posted on 2006-07-13 11:06 SIMONE 閱讀(908) 評論(0)  編輯  收藏 所屬分類: JAVAJSP
    主站蜘蛛池模板: 色婷婷7777免费视频在线观看| 黄色三级三级三级免费看| 亚洲男人天堂2017| 国产精一品亚洲二区在线播放| 中文字幕亚洲激情| 亚洲午夜久久久影院伊人 | 亚洲国产精品99久久久久久 | 国产小视频免费观看| 日本一区免费电影| 免费永久国产在线视频| 免费人成年激情视频在线观看| mm1313亚洲精品无码又大又粗 | a毛片免费全部播放完整成| 国产日韩久久免费影院| 日韩a级无码免费视频| 日本一区二区免费看| 99精品国产成人a∨免费看| 97在线视频免费公开观看| 91免费国产在线观看| 日韩精品成人无码专区免费| 女人18毛片a级毛片免费| 国产在线19禁免费观看国产 | 国产免费人成视频尤勿视频| 三上悠亚电影全集免费| 99在线观看视频免费| 波多野结衣免费在线观看| 女人与禽交视频免费看| 四虎AV永久在线精品免费观看| 亚洲精品亚洲人成在线观看下载| 亚洲国产精品自在自线观看| 亚洲AV一区二区三区四区| 水蜜桃视频在线观看免费| 91视频免费观看高清观看完整| 91人人区免费区人人| 成人性生活免费视频| 亚洲熟妇少妇任你躁在线观看无码 | 亚洲午夜在线播放| 色妞www精品视频免费看| a级毛片毛片免费观看永久| 6080午夜一级毛片免费看6080夜福利| 中文字幕在线免费播放|