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

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

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

    WEB開(kāi)發(fā) de 點(diǎn)滴

    by sanwish

      BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      38 隨筆 :: 0 文章 :: 4 評(píng)論 :: 0 Trackbacks
    Jakarta Commons:巧用類(lèi)和組件

    Jakarta Commons 是Jakarta 的子項(xiàng)目,它創(chuàng)建和維護(hù)著許多獨(dú)立軟件包,這些包一般與其他框架或產(chǎn)品無(wú)關(guān),其中收集了大量小型、實(shí)用的組件,大部分面向服務(wù)器端編程。

    Commons的包分成兩部分:Sandbox,Commons 代碼庫(kù)。Sandbox 是一個(gè)測(cè)試平臺(tái),用來(lái)檢驗(yàn)各種設(shè)想、計(jì)劃。本文介紹的組件屬于Commons代碼庫(kù),文章將展示各個(gè)組件的功能、適用場(chǎng)合,并通過(guò)簡(jiǎn)單的例子介紹其用法。

    一、概述
    可重用性是Jakarta Commons 項(xiàng)目的靈魂所在。這些包在設(shè)計(jì)階段就已經(jīng)考慮了可重用性問(wèn)題。其中一些包,例如Commons 里面用來(lái)記錄日志的Logging包,最初是為其他項(xiàng)目設(shè)計(jì)的,例如Jakarta Struts項(xiàng)目,當(dāng)人們發(fā)現(xiàn)這些包對(duì)于其他項(xiàng)目也非常有用,能夠極大地幫助其他項(xiàng)目的開(kāi)發(fā),他們決定為這些包構(gòu)造一個(gè)"公共"的存放位置,這就是Jakarta Commons項(xiàng)目。為了真正提高可重用性,每一個(gè)包都必須不依賴(lài)于其他大型的框架或項(xiàng)目。因此,Commons項(xiàng)目的包基本上都是獨(dú)立的,不僅是相對(duì)于其他項(xiàng)目的獨(dú)立,而且相對(duì)于Commons內(nèi)部的大部分其他包獨(dú)立。雖然存在一些例外的情況,例如Betwixt 包要用到XML API,但絕大部分只使用最基本的API,其主要目的就是要能夠通過(guò)簡(jiǎn)單的接口方便地調(diào)用。
    不過(guò)由于崇尚簡(jiǎn)潔,許多包的文檔變得過(guò)于簡(jiǎn)陋,缺乏維護(hù)和支持,甚至有一部分還有錯(cuò)誤的鏈接,文檔也少得可憐。大部分的包需要我們自己去找出其用法,甚至有時(shí)還需要我們自己去分析其適用場(chǎng)合。本文將逐一介紹這些包,希望能夠幫助你迅速掌握這一積累了許多人心血的免費(fèi)代碼庫(kù)。

    說(shuō)明:Jakarta Commons 和Apache Commons 是不同的,后者是Apache Software Foundation的一個(gè)頂層項(xiàng)目,前者則是Jakarta 項(xiàng)目的一個(gè)子項(xiàng)目,同是也是本文要討論的主角。本文后面凡是提到Commons的地方都是指Jakarta 的Commons。為了便于說(shuō)明,本文把Commons 項(xiàng)目十八個(gè)成品級(jí)的組件(排除了EL、Latka和Jexl)分成5類(lèi),

    必須指出的是,這種分類(lèi)只是為了方便文章說(shuō)明,Commons 項(xiàng)目里面實(shí)際上并不存在這種分類(lèi),同時(shí)這些分類(lèi)的邊界有時(shí)也存在一定的重疊。本文首先介紹Web 相關(guān)類(lèi)和其他類(lèi)里面的組件,下一篇文章將涉及XML 相關(guān)、包裝這兩類(lèi),最后一篇文章專(zhuān)門(mén)介紹屬于工具類(lèi)的包。

    二、其他類(lèi)
    CLI、Discovery、Lang 和Collections 包歸入其他類(lèi),這是因?yàn)樗鼈兌几髯葬槍?duì)某個(gè)明確、實(shí)用的小目標(biāo),可謂專(zhuān)而精。
    2.1 CLI
    ■ 概況:CLI 即Command Line Interface,也就是"命令行接口",它為Java 程序訪問(wèn)和解析命令行參數(shù)提供了一種統(tǒng)一的接口。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼
    ■ 何時(shí)適用:當(dāng)你需要以一種一致的、統(tǒng)一的方式訪問(wèn)命令行參數(shù)之時(shí)。
    ■ 示例應(yīng)用:CLIDemo.java。CLASSPATH 中必須包含commons-cli-1.0.jar。
    ■ 說(shuō)明:
    有多少次你不得不為一個(gè)新的應(yīng)用程序重新設(shè)計(jì)新的命令行參數(shù)處理方式?如果能夠只用某個(gè)單一的接口,統(tǒng)一完成諸如定義輸入?yún)?shù)(是否為強(qiáng)制參數(shù),數(shù)值還是字符串,等等)、根據(jù)一系列規(guī)則分析參數(shù)、確定應(yīng)用要采用的路徑等任務(wù),那該多好!答案就在CLI。在CLI中,每一個(gè)想要在命令中指定的參數(shù)都是一個(gè)Option對(duì)象。首先創(chuàng)建一個(gè)Options 對(duì)象,將各個(gè)Option對(duì)象加入Options對(duì)象,然后利用CLI提供的方法來(lái)解析用戶的輸入?yún)?shù)。Option對(duì)象可以要求用戶必須輸入某個(gè)參數(shù),例如必須在命令行提供文件名字。如果某個(gè)參數(shù)是必須的,創(chuàng)建Option 對(duì)象的時(shí)候就要顯式地指定。
    下面是使用CLI 的步驟。
    // …
    // ① 創(chuàng)建一個(gè)Options:
    Options options = new Options();
    options.addOption("t", false, "current time");
    // …
    // ② 創(chuàng)建一個(gè)解析器,分析輸入:
    CommandLineParser parser = new BasicParser();
    CommandLine cmd;
    try {
    cmd = parser.parse(options, args);
    } catch (ParseException pe) {
    usage(options);
    return;
    }
    // …
    // ③ 最后就可以根據(jù)用戶的輸入,采取相應(yīng)的操作:
    if (cmd.hasOption("n")) {
    System.err.println("Nice to meet you: " +
    cmd.getOptionValue('n'));
    }
    這就是使用CLI的完整過(guò)程了。當(dāng)然,CLI 還提供了其他高級(jí)選項(xiàng),例如控制格式和解析過(guò)程等,
    但基本的使用思路仍是一致的。

    2.2 Discovery
    ■ 概況:Discovery 組件是發(fā)現(xiàn)模式(Discovery Pattern)的一個(gè)實(shí)現(xiàn),它的目標(biāo)是按照一種統(tǒng)一的方式定位和實(shí)例化類(lèi)以及其他資源。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你想用最佳的算法在Java程序中查找Java接口的各種實(shí)現(xiàn)之時(shí)。
    ■ 應(yīng)用實(shí)例:DiscoveryDemo.java,MyInterface.java,MyImpl1.java,MyImpl2.java,MyInterface。要求CLASSPATH 中必須包含commons-discovery.jar和commons-logging.jar。
    ■ 說(shuō)明:

    Discovery 的意思就是"發(fā)現(xiàn)",它試圖用最佳的算法查找某個(gè)接口的所有已知的實(shí)現(xiàn)。在使用服務(wù)的場(chǎng)合,當(dāng)我們想要查找某個(gè)服務(wù)的所有已知的提供者時(shí) Discovery 組件尤其有用。考慮一下這種情形:我們?yōu)槟硞€(gè)特別復(fù)雜的任務(wù)編寫(xiě)了一個(gè)接口,所有該接口的實(shí)現(xiàn)都用各不相同的方式來(lái)完成這個(gè)復(fù)雜任務(wù),最終用戶可以根據(jù)需要來(lái)選擇完成任務(wù)的具體方式。那么,在這種情形下,最終用戶應(yīng)該用什么辦法來(lái)找出接口的所有可用實(shí)現(xiàn)(即可能的完成任務(wù)的方式)呢?

    上面描述的情形就是所謂的服務(wù)-服務(wù)提供者體系。服務(wù)的功能由接口描述,服務(wù)提供者則提供具體的實(shí)現(xiàn)。現(xiàn)在的問(wèn)題是最終用戶要用某種辦法來(lái)尋找系統(tǒng)中已經(jīng)安裝了哪些服務(wù)提供者。在這種情形下,Discovery 組件就很有用了,它不僅可以用來(lái)查找那些實(shí)現(xiàn)了特定接口的類(lèi),而且還可以用來(lái)查找資源,例如圖片或其他文件等。在執(zhí)行這些操作時(shí),Discovery遵從Sun的服務(wù)提供者體系所定義的規(guī)則。由于這個(gè)原因,使用Discovery 組件確實(shí)帶來(lái)許多方便。

    請(qǐng)讀者參閱本文后面示例程序中的接口MyInterface.java 和兩個(gè)實(shí)現(xiàn)類(lèi)MyImpl1.java、MyImple2.java,了解下面例子的細(xì)節(jié)。在使用Discovery的時(shí)候要提供MyInterface 文件,把它放入META-INF/services目錄,注意該文件的名字對(duì)應(yīng)接口的完整限定名稱(chēng)(Fully Qualified Name),如果接口屬于某個(gè)包,該文件的名字也必須相應(yīng)地改變。

    // …
    // ① 創(chuàng)建一個(gè)類(lèi)裝入器的實(shí)例。
    ClassLoaders loaders =
    ClassLoaders.getAppLoaders(MyInterface.class, getClass(), false);
    // …
    // ② 用DiscoverClass 的實(shí)例來(lái)查找實(shí)現(xiàn)類(lèi)。
    DiscoverClass discover = new DiscoverClass(loaders);
    // …
    // ③ 查找實(shí)現(xiàn)了指定接口的類(lèi):
    Class implClass = discover.find(MyInterface.class);
    System.err.println("Implementing Provider: " + implClass.getName());
    運(yùn)行上面的代碼,就可以得到在MyInterface 文件中注冊(cè)的類(lèi)。再次提醒,如果你的實(shí)現(xiàn)是封裝在包里面的,在這里注冊(cè)的名字也應(yīng)該作相應(yīng)地修改,如果該文件沒(méi)有放在正確的位置,或者指定名字的實(shí)現(xiàn)類(lèi)不能找到或?qū)嵗绦驅(qū)伋鯠iscoverException,表示找不到符合條件的實(shí)現(xiàn)。

    下面是MyInterface文件內(nèi)容的一個(gè)例子:MyImpl2 # Implementation 2。
    當(dāng)然,實(shí)現(xiàn)類(lèi)的注冊(cè)辦法并非只有這么一種,否則的話Discovery 的實(shí)用性就要大打折扣了!實(shí)際上,按照Discovery 內(nèi)部的類(lèi)查找機(jī)制,按照這種方法注冊(cè)的類(lèi)將是Discovery 最后找到的類(lèi)。另一種常用的注冊(cè)方法是通過(guò)系統(tǒng)屬性或用戶定義的屬性來(lái)傳遞實(shí)現(xiàn)類(lèi)的名字,例如,放棄META-INF/services 目錄下
    的文件,改為執(zhí)行java -DMyInterface=MyImpl1 DiscoveryDemo命令來(lái)運(yùn)行示例程序,這里的系統(tǒng)屬性是接口的名字,值是該接口的提供者,運(yùn)行的結(jié)果是完全一樣的。

    Discovery 還可以用來(lái)創(chuàng)建服務(wù)提供者的(singleton)實(shí)例并調(diào)用其方法,語(yǔ)法如下:
    ((MyInterface)discover.newInstance(MyInterface.class)).myMethod();。注意在這個(gè)例子中,我們并不知道到底哪一個(gè)服務(wù)提供者實(shí)現(xiàn)了myMethod,甚至我們根本不必關(guān)心這一點(diǎn)。具體的情形與運(yùn)行這段代碼的方式以及運(yùn)行環(huán)境中已經(jīng)注冊(cè)了什么服務(wù)提供者有關(guān),在不同的環(huán)境下運(yùn)行,實(shí)際得到的服務(wù)提供者可能不同。

    2.3 Lang
    ■ 概況:Lang是java.lang 的一個(gè)擴(kuò)展包,增加了許多操作String的功能,另外還支持C 風(fēng)格的枚舉量

    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)java.lang 包提供的方法未能滿足需要,想要更多的功能來(lái)處理String、數(shù)值和System 屬性時(shí);還有,當(dāng)你想要使用C風(fēng)格的枚舉量時(shí)。
    ■ 示例應(yīng)用:LangDemo.java,Mortgage.java,OnTV.java。CLASSPATH中必須包含commons-lang.jar。
    ■ 說(shuō)明:

    這個(gè)包提供了許多出于方便目的而提供的方法,它們中的大多數(shù)是靜態(tài)的,簡(jiǎn)化了日常編碼工作。StringUtils類(lèi)是其中的一個(gè)代表,它使得開(kāi)發(fā)者能夠超越標(biāo)準(zhǔn)的java.lang.String 包來(lái)處理字符串。使用這些方法很簡(jiǎn)單,通常只要在調(diào)用靜態(tài)方法時(shí)提供適當(dāng)?shù)膮?shù)就可以了。例如,如果要將某個(gè)單詞的首字符改為大寫(xiě),只需調(diào)用:StringUtils.capitalise("name"),調(diào)用的輸出結(jié)果是Name。請(qǐng)瀏覽
    StringUtils API 文檔了解其他靜態(tài)方法,也許你會(huì)找到一些可以直接拿來(lái)使用的代碼。本文提供的示例程序示范了其中一些方法的使用。

    另一個(gè)值得注意的類(lèi)是RandomStringUtils,它提供了生成隨機(jī)字符串的方法,用來(lái)創(chuàng)建隨機(jī)密碼實(shí)在太方便了。NumberUtils 類(lèi)提供了處理數(shù)值數(shù)據(jù)的方法,許多方法值得一用,例如尋找最大、最小數(shù)的方法,將String 轉(zhuǎn)換成數(shù)值的方法,等等。NumberRange和CharRange類(lèi)分別提供了創(chuàng)建和操作數(shù)值范圍、字符范
    圍的方法。

    Builder包里的類(lèi)提供了一些特殊的方法,可用來(lái)構(gòu)造類(lèi)的toString、hashCode、compareTo 和equals方法,其基本思路就是構(gòu)造出類(lèi)的高質(zhì)量的toString、hashCode、compareTo 和equals 方法,從而免去了用戶自己定義這些方法之勞,只要調(diào)用一下Builder 包里面的方法就可以了。例如,我們可以用
    ToStringBuilder 來(lái)構(gòu)造出類(lèi)的toString描述,如下例所示:
    public class Mortgage {
    private float rate;
    private int years;
    ....
    public String toString() {
    return new ToStringBuilder(this).
    append("rate", this.rate).
    append("years", this.years).
    toString();
    }
    }
    使用這類(lèi)方法有什么好處呢?顯然,它使得我們有可能通過(guò)一種統(tǒng)一的方式處理所有數(shù)據(jù)類(lèi)型。所有Builder 方法的用法都和上例相似。

    Java 沒(méi)有C 風(fēng)格的枚舉量,為此,lang 包提供了一個(gè)類(lèi)型安全的Enum 類(lèi)型,填補(bǔ)了空白。Enum 類(lèi)是抽象的,如果你要?jiǎng)?chuàng)建枚舉量,就要擴(kuò)展Enum 類(lèi)。下面的例子清楚地說(shuō)明了Enum 的用法。
    import org.apache.commons.lang.enum.Enum;
    import java.util.Map;
    import java.util.List;
    import java.util.Iterator;
    public final class OnTV extends Enum {
    public static final OnTV IDOL=
    new OnTV("Idol");
    public static final OnTV SURVIVOR =
    new OnTV("Survivor");
    public static final OnTV SEINFELD =
    new OnTV("Seinfeld");
    private OnTV(String show) {
    super(show);
    }
    public static OnTV getEnum(String show){
    return (OnTV) getEnum(OnTV.class, show);
    }
    public static Map getEnumMap() {
    return getEnumMap(OnTV.class);
    }
    public static List getEnumList() {
    return getEnumList(OnTV.class);
    }
    public static Iterator iterator() {
    return iterator(OnTV.class);
    }
    }
    以后我們就可以按照下面的方式使用枚舉變量:OnTV.getEnum("Idol")。該調(diào)用從前面創(chuàng)建的枚舉數(shù)據(jù)
    類(lèi)型返回Idol。這個(gè)例子比較簡(jiǎn)單,實(shí)際上Enum類(lèi)還提供了許多有用的方法,請(qǐng)參見(jiàn)本文后面提供的
    完整實(shí)例。

    2.4 Collections
    ■ 概況:擴(kuò)展了Java Collection框架,增添了新的數(shù)據(jù)結(jié)構(gòu)、迭代機(jī)制和比較操作符。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:幾乎所有需要操作數(shù)據(jù)結(jié)構(gòu)的重要Java開(kāi)發(fā)項(xiàng)目都可以使用Collections API。和Java的標(biāo)準(zhǔn)實(shí)現(xiàn)相比,Collections API 有著諸多優(yōu)勢(shì)。
    ■ 示例應(yīng)用:CollectionsDemo.java。要求CLASSPATH 中包含commons-collections.jar。
    ■ 說(shuō)明:
    要在有限的文章篇幅之內(nèi)詳盡地介紹Collections API 實(shí)在是太困難了,不過(guò)這里仍將涵蓋大多數(shù)最重要的類(lèi),希望能夠引起你的興趣,認(rèn)真了解一下其余的類(lèi)。Collections 本身的文檔也提供了許多資料并解釋了每一個(gè)類(lèi)的用法。

    Bag 接口擴(kuò)展標(biāo)準(zhǔn)的Java Collection,允許生成計(jì)數(shù)器來(lái)跟蹤Bag里面的所有元素。當(dāng)你想要跟蹤進(jìn)出某個(gè)集合的元素的總數(shù)時(shí),Bag 是非常有用的。由于Bag本身是一個(gè)接口,所以實(shí)際使用的應(yīng)該是實(shí)現(xiàn)了該接口的類(lèi),例如HashBag 或TreeBag--從這些類(lèi)的名字也可以看出,HashBag 實(shí)現(xiàn)的是一個(gè)HashMap的Bag,而TreeBag實(shí)現(xiàn)的是TreeMap的Bag。Bag 接口中兩個(gè)最重要的方法是:

    getCount(Object o),

    用來(lái)返回Bag 里面特定對(duì)象的出現(xiàn)次數(shù);uniqueSet(),返回所有唯一元素。
    Buffer接口允許按照預(yù)定義的次序刪除集合中的對(duì)象,刪除次序可以是LIFO(Last In First Out,后進(jìn)先出),或FIFO(First In First Out,先進(jìn)先出),另外還可以是自定義的次序。下面來(lái)看看如何實(shí)現(xiàn)一個(gè)Buffer,按照自然次序刪除元素。
    BinaryHeap 類(lèi)實(shí)現(xiàn)了Buffer 接口,能夠按照自然次序刪除元素。如果要顛倒次序,則必須傳入一個(gè)false,告訴Heap 采用自然次序的逆序。

    BinaryHeap heap = new BinaryHeap();
    // …
    // 將元素加入該Heap
    heap.add(new Integer(-1));
    heap.add(new Integer(-10));
    heap.add(new Integer(0));
    heap.add(new Integer(-3));
    heap.add(new Integer(5));
    //…
    // 刪除一個(gè)元素
    heap.remove();
    調(diào)用該Heap 的remove,按照自然次序,元素集合中的-10將被刪除。如果我們要求按照逆序排序,則被刪除的將是5。FastArrayList、FastHashMap 和FastTreeMap 類(lèi)能夠按照兩種模式操作,超越了與它們對(duì)應(yīng)的標(biāo)準(zhǔn)Collection。第一種模式是"慢模式",類(lèi)的修改操作(添加、刪除元素)是同步的。與此相對(duì),另一種模 式是"快模式",對(duì)這些類(lèi)的訪問(wèn)假定為只讀操作,因此不需要同步,速度較快。在快模式中,結(jié)構(gòu)性的改動(dòng)通過(guò)下列方式完成:首先克隆現(xiàn)有的類(lèi),修改克隆得到的類(lèi),最后用克隆得到的類(lèi)替換原有的類(lèi)。

    FastArrayList、FastHashMap和FastTreeMap 類(lèi)特別適合于那種初始化之后大部分操作都是只讀操作的多線程環(huán)境。

    iterators 包為各種集合和對(duì)象提供標(biāo)準(zhǔn)Java Collection 包沒(méi)有提供的迭代器。本文的示例應(yīng)用示范了ArrayIterator,通過(guò)迭代方式訪問(wèn)Array的內(nèi)容。iterators 包里面各種迭代器的用法基本上與標(biāo)準(zhǔn)Java 迭代器一樣。

    最后,comparators 包提供了一些實(shí)用的比較符。所謂比較符其實(shí)也是一個(gè)類(lèi),它定義的是如何比較兩個(gè)屬于同一類(lèi)的對(duì)象,決定它們的排序次序。例如,在前面提到的Buffer 類(lèi)中,我們可以定義自己的比較符,用自定義的比較符來(lái)決定元素的排序次序,而不是采用元素的自然排序次序。下面來(lái)看看具體的
    實(shí)現(xiàn)經(jīng)過(guò)。
    // …
    // ① 創(chuàng)建一個(gè)BinaryHeap 類(lèi),但這一次參數(shù)中
    // 指定NullComparator。NullComparator比較
    // null與其他對(duì)象,根據(jù)nullsAreHigh 標(biāo)記來(lái)
    // 判斷null 值比其他對(duì)象大還是小:如果
    // nullsAreHigh的值是false,則認(rèn)為null 要比
    // 其他對(duì)象小。
    BinaryHeap heap2 = new BinaryHeap
    (new NullComparator(false));
    // …
    // ② 將一些數(shù)據(jù)(包括幾個(gè)null 值)加入heap:
    heap2.add(null);
    heap2.add(new Integer("6"));
    heap2.add(new Integer("-6"));
    heap2.add(null);
    // …
    // ③ 最后刪除一個(gè)元素,Bag 包含的null 將減少
    // 一個(gè),因?yàn)閚ull 要比其他對(duì)象小。
    heap2.remove();
    有關(guān)其他類(lèi)Commons 組件的介紹就到這里結(jié)束。如果你想了解更多細(xì)節(jié)信息,請(qǐng)參見(jiàn)API文檔,最好再看看這些包的源代碼。


    三、Web類(lèi)
    Web 類(lèi)的組件用來(lái)執(zhí)行與Web 相關(guān)的任務(wù)。
    3.1 FileUpload
    ■ 概況:一個(gè)可以直接使用的文件上載組件。
    ■ 官方資源:主頁(yè)。由于這個(gè)組件尚未正式發(fā)布,今年二月發(fā)布的Beta版又有許多BUG,所以建議從nightly builds 下載最新的版本。
    ■ 何時(shí)適用:當(dāng)你想要在Java 服務(wù)器環(huán)境中加入一個(gè)易用、高性能的文件上載組件之時(shí)。
    ■ 示例應(yīng)用:fileuploaddemo.jsp,fileuploaddemo.htm,和msg.jsp。要求服務(wù)器端應(yīng)用目錄的WEB-INF/lib下面有commons-fileupload-1.0-dev.jar。
    ■ 說(shuō)明:
    FileUpload 組件解決了常見(jiàn)的文件上載問(wèn)題。它提供了一個(gè)易用的接口來(lái)管理上載到服務(wù)器的文件,
    可用于JSP和Servlet 之中。FileUpload 組件遵從RFC1867,它分析輸入請(qǐng)求,向應(yīng)用程序提供一系列上載到服務(wù)器的文件。上載的文件可以保留在內(nèi)存中,也可以放入一個(gè)臨時(shí)位置(允許配置一個(gè)表示文件大小的參數(shù),如果上載的文件超過(guò)了該參數(shù)指定的大小,則把文件寫(xiě)入一個(gè)臨時(shí)位置)。另外還有一些參數(shù)可供配置,包括可接受的最大文件、臨時(shí)文件的位置等。

    下面介紹一下使用FileUpload 組件的步驟。
    首先創(chuàng)建一個(gè)HTML 頁(yè)面。注意,凡是要上載文件的表單都必須設(shè)置enctype屬性,且屬性的值必須是multipart/form-data,同時(shí)請(qǐng)求方法必須是POST。下面的表單除了上載兩個(gè)文件,另外還有一個(gè)普通的文本輸入框:

    <form name="myform" action="fileuploaddemo.jsp"
    method="post" enctype="multipart/form-data">
    輸入你的名字:<br />
    <input type="text" name="name" size="15"/><br />
    圖形:<br />
    <input type="file" name="myimage"><br/>
    文件:<br />
    <input type="file" name="myfile"><br /><br />
    <input type="submit" name="Submit"
    value="Submit your files"/>
    接下來(lái)創(chuàng)建JSP頁(yè)面。
    // …
    // ① 檢查輸入請(qǐng)求是否為multipart的表單數(shù)據(jù)。
    boolean isMultipart = FileUpload.
    isMultipartContent(request);
    // …
    // ② 為該請(qǐng)求創(chuàng)建一個(gè)句柄,通過(guò)它來(lái)解析請(qǐng)求。執(zhí)行
    // 解析后,所有的表單項(xiàng)目都保存在一個(gè)List中。
    DiskFileUpload upload = new DiskFileUpload();
    // 通過(guò)句柄解析請(qǐng)求,解析得到的項(xiàng)目保存在一個(gè)List 中
    List items = upload.parseRequest(request);
    // …
    // ③ 通過(guò)循環(huán)依次獲得List里面的文件項(xiàng)目。要區(qū)分表示
    // 文件的項(xiàng)目和普通的表單輸入項(xiàng)目,使用isFormField()
    // 方法。根據(jù)處理請(qǐng)求的要求,我們可以保存上載的文
    // 件,或者一個(gè)字節(jié)一個(gè)字節(jié)地處理文件內(nèi)容,或者打
    // 開(kāi)文件的輸入流。
    Iterator itr = items.iterator();
    while(itr.hasNext()) {
    FileItem item = (FileItem) itr.next();
    // 檢查當(dāng)前的項(xiàng)目是普通的表單元素,還是一個(gè)上載的文件
    if(item.isFormField()) {
    // 獲得表單域的名字
    String fieldName = item.getFieldName();
    // 如果表單域的名字是name…
    if(fieldName.equals("name"))
    request.setAttribute("msg",
    "Thank You: " + item.getString());
    } else {
    // 該項(xiàng)目是一個(gè)上載的文件,把它保存到磁盤(pán)。
    // 注意item.getName()
    // 會(huì)返回上載文件在客戶端的完整路徑名稱(chēng),這似乎是一個(gè)BUG。
    // 為解決這個(gè)問(wèn)題,這里使用了fullFile.getName()。
    File fullFile = new File(item.getName());
    File savedFile = new File
    (getServletContext().getRealPath("/"),
    fullFile.getName());
    item.write(savedFile);
    }
    }
    我們可以通過(guò)上載句柄的upload.setSizeMax 來(lái)限制上載文件的大小。當(dāng)上載文件的大小超過(guò)允許的值時(shí),程序?qū)⒂龅疆惓!T谏厦娴睦又校募笮〉南拗浦凳?1,表示允許上載任意大小的文件。還有其他一些略有變化的使用形式,正如前面所指出的,我們可以在上載的文件上打開(kāi)一個(gè)輸入流,或者讓它們駐留在內(nèi)存中直至空間占用達(dá)到一定的限制值,或者在判斷文件類(lèi)型的基礎(chǔ)上,以String 或Byte 數(shù)組的形式獲取其內(nèi)容,或者直接刪除文件。這一切都只要使用FileItem 類(lèi)提供的方法就可以方便地做到(DefaultFileItem 是FileItem的一個(gè)實(shí)現(xiàn))。

    3.2 HttpClient
    ■ 概況:這個(gè)API 擴(kuò)展了java.net包,提供了模擬瀏覽器的功能。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你要構(gòu)造Web 瀏覽器的功能;當(dāng)你的應(yīng)用需要一種高效的辦法進(jìn)行HTTP/HTTPS通信時(shí)。
    ■ 示例應(yīng)用:HttpClientDemo.java。要求CLASSPATH中有commons-httpclient.jar,common-logging.jar。要求使用JDK 1.4 或更高版本。
    ■ 說(shuō)明:
    HttpClient 擴(kuò)展和增強(qiáng)了標(biāo)準(zhǔn)java.net 包,是一個(gè)內(nèi)容廣泛的代碼庫(kù),功能極其豐富,能夠構(gòu)造出各種使用HTTP 協(xié)議的分布式應(yīng)用,或者也可以嵌入到現(xiàn)有應(yīng)用,為應(yīng)用增加訪問(wèn)HTTP 協(xié)議的能力。在Commons 穩(wěn)定版中,HttpClient 的文檔似乎要比其他包更完善一些,而且還帶有幾個(gè)實(shí)例。下面我們 通過(guò)
    一個(gè)簡(jiǎn)單的例子來(lái)了解如何提取一個(gè)Web 頁(yè)面,HttpClient 文檔中也有一個(gè)類(lèi)似的例子,我們將擴(kuò)充那個(gè)例子使其支持SSL。注意本例需要JDK 1.4 支持,因?yàn)樗玫絁ava Secure Socket Connection庫(kù),而這個(gè)庫(kù)只有JDK 1.4 及更高的版本才提供。

    ① 首先確定一個(gè)可以通過(guò)HTTPS 下載的頁(yè)面,本例使用的是https://www.paypal.com/%E3%80%82%E5%90%8C%E6%97%B6%E7%A1%AE%E4%BF%9D
    %JAVA_HOME%/jre/lib/security/java.security文件包含了下面這行代碼:
    security.provider.2=com.sun.net.ssl.internal.ssl.Provider。
    除了這些設(shè)置之外,HTTPS連接的處理方式?jīng)]有其他特別的地方--至少對(duì)于本例來(lái)說(shuō)如此。不過(guò),如果遠(yuǎn)程網(wǎng)站使用的根證書(shū)不被你使用的Java 認(rèn)可,則首先必須導(dǎo)入它的證書(shū)。
    ② 創(chuàng)建一個(gè)HttpClient的實(shí)例。HttpClient 類(lèi)可以看成是應(yīng)用的主驅(qū)動(dòng)程序,所有針對(duì)網(wǎng)絡(luò)的功能都依賴(lài)于它。HttpClient 類(lèi)需要一個(gè)Connection Manager來(lái)管理連接。
    HttpConnectionManager允許我們創(chuàng)建自己的連接管理器,或者,我們也可以直接使用內(nèi)建的 SimpleHttpConnectionManager或MultiThreadedHttpConnectionManager類(lèi)。如果在創(chuàng)建 HttpClient 時(shí)沒(méi)有指定連接管理器,HttpClient默認(rèn)使用SimpleHttpConnectionManager。
    // 創(chuàng)建一個(gè)HttpClient 的實(shí)例
    HttpClient client = new HttpClient();

    ③ 創(chuàng)建一個(gè)HttpMethod的實(shí)例,即確定與遠(yuǎn)程服務(wù)器的通信要采用哪種傳輸方式,HTTP 允許采用的傳輸方式包括:GET,POST,PUT,DELETE,HEAD,OPTIONS,以及TRACE。這些傳輸方式分別作為一個(gè)獨(dú)立的類(lèi)實(shí)現(xiàn),但所有這些類(lèi)都實(shí)現(xiàn)HttpMethod接口。在本例中,我們使用的是GetMethod,創(chuàng)建GetMethod
    實(shí)例時(shí)在參數(shù)中指定我們想要GET 的URL。
    // 創(chuàng)建一個(gè)HttpMethod 的實(shí)例
    HttpMethod method = new GetMethod(url);

    ④ 執(zhí)行HttpMethod 定義的提取操作。執(zhí)行完畢后,executeMethod方法將返回遠(yuǎn)程服務(wù)器報(bào)告的狀態(tài)代碼。注意executeMethod屬于HttpClient,而不是HttpMethod。
    // 執(zhí)行HttpMethod定義的提取操作
    statusCode = client.executeMethod(method);

    ⑤ 讀取服務(wù)器返回的應(yīng)答。如果前面的連接操作失敗,程序?qū)⒂龅紿ttpException或IOException,其中IOException 一般意味著網(wǎng)絡(luò)出錯(cuò),繼續(xù)嘗試也不太可能獲得成功。服務(wù)器返回的應(yīng)答可以按照多種方式讀取,例如作為一個(gè)字節(jié)數(shù)組,作為一個(gè)輸入流,或者作為一個(gè)String。獲得服務(wù)器返回的應(yīng)答后,我們就可以按照自己的需要任意處置它了。
    byte[] responseBody = method.getResponseBody();

    ⑥ 最后要做的就是釋放連接。
    method.releaseConnection();
    以上只是非常簡(jiǎn)單地介紹了一下HttpClient 庫(kù),HttpClient 實(shí)際的功能要比本文介紹的豐富得多,不僅健壯而且高效,請(qǐng)參閱API 文檔了解詳情。


    3.3 Net
    ■ 概況:一個(gè)用于操作Internet基礎(chǔ)協(xié)議的底層API。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你想要訪問(wèn)各種Internet底層協(xié)議之時(shí)(Finger,Whois,TFTP,Telnet,POP3,F(xiàn)TP,NNTP,以及SMTP)。
    ■ 示例應(yīng)用:NetDemo.java。要求CLASSPATH中包含commons-net-1.0.0.jar。
    ■ 說(shuō)明:
    Net 包是一個(gè)強(qiáng)大、專(zhuān)業(yè)的類(lèi)庫(kù),類(lèi)庫(kù)里的類(lèi)最初屬于一個(gè)叫做NetComponents 的商業(yè)產(chǎn)品。Net 包不僅支持對(duì)各種低層次協(xié)議的訪問(wèn),而且還提供了一個(gè)高層的抽象。大多數(shù)情況下,Net包提供的抽象已能滿足一般需要,它使得開(kāi)發(fā)者不再需要直接面對(duì)各種協(xié)議的Socket 級(jí)的低層命令。使用高層抽象并不減少任何功能,Net API 在這方面做得很出色,既提供了足夠的功能,又不至于在特色方面作過(guò)多的妥協(xié)。

    SocketClient 是支持所有協(xié)議的基礎(chǔ)類(lèi),它是一個(gè)抽象類(lèi),聚合了各種協(xié)議都需要的公用功能。各種不同協(xié)議的使用過(guò)程其實(shí)很相似,首先利用connect方法建立一個(gè)指向遠(yuǎn)程服務(wù)器的連接,執(zhí)行必要的操作,最后終止與服務(wù)器的連接。下面通過(guò)實(shí)例介紹具體的使用步驟。
    // …
    // ① 創(chuàng)建一個(gè)客戶端。我們將用NNTPClient
    // 從新聞服務(wù)器下載新聞組清單。
    client = new NNTPClient();
    // …
    // ② 利用前面創(chuàng)建的客戶端連接到新聞服務(wù)器。
    // 這里選用的是一個(gè)新聞組較少的服務(wù)器。
    client.connect("aurelia.deine.net");
    // …
    // ③ 提取新聞組清單。下面的命令將返回一個(gè)
    // NewsGroupInfo 對(duì)象的數(shù)組。如果指定的服
    // 務(wù)器上不包含新聞組,返回的數(shù)組將是空的,
    // 如果遇到了錯(cuò)誤,則返回值是null。
    list = client.listNewsgroups();
    //...
    // ④ 最后終止與服務(wù)器的連接。
    if (client.isConnected())
    client.disconnect();
    必須說(shuō)明的是,listNewsgroups命令可能需要較長(zhǎng)的時(shí)間才能返回,一方面是因?yàn)榫W(wǎng)絡(luò)速度的影響,另外也可能是由于新聞組清單往往是很龐大的。NewsGroupInfo對(duì)象包含有關(guān)新聞組的詳細(xì)信息,并提供了一些操作新聞組的命令,比如提取文章總數(shù)、最后發(fā)布的文章、發(fā)布文章的權(quán)限,等等。

    其他客戶端,例如FingerClient、POP3Client、TelnetClient 等,用法也差不多。
    結(jié)束語(yǔ):有關(guān)Web相關(guān)類(lèi)和其他類(lèi)的介紹就到此結(jié)束。在下一篇文章中,我們將探討XML類(lèi)和包裝類(lèi),最后一篇文章則介紹工具類(lèi)。
    希望讀者有興趣試試本文提供的程序?qū)嵗:芏鄷r(shí)候Jakarta Commons 給人以混亂的感覺(jué),希望本文使你加深了對(duì)Jakarta Commons 了解,或者至少引起了你對(duì)Commons 子項(xiàng)目以及它提供的各種實(shí)用API 和
    庫(kù)的興趣。



    第二部分XML 類(lèi)和包裝類(lèi)
    上一篇文章中,我們將Jakarta Commons的組件分成了五類(lèi),并介紹了其中的Web類(lèi)和其他類(lèi),本文接著介紹XML 類(lèi)和包裝類(lèi),接下來(lái)的最后一篇文章將介紹工具類(lèi)。注意Commons本身并不進(jìn)行這種分類(lèi),這里進(jìn)行分類(lèi)純粹是為組織方便起見(jiàn)。

    一、包裝類(lèi)
    這一類(lèi)包含Codec 和Modeler 兩個(gè)組件。
    1.1 Codec
    ■ 概況:提供常用的編碼器和解碼器。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你需要Base64 和Hex編碼功能的標(biāo)準(zhǔn)實(shí)現(xiàn)之時(shí)。
    ■ 示例應(yīng)用:CodecDemo.java。要求CLASSPATH必須包含commons-codec-1.1.jar。
    ■ 說(shuō)明:
    Codec 里面的類(lèi)分成兩個(gè)包,其中一個(gè)包實(shí)現(xiàn)的是常用的Base64 和Hex 編碼機(jī)制,另一個(gè)包是語(yǔ)言、語(yǔ)音方面的編碼。兩個(gè)包的用法相似,鑒于語(yǔ)言、語(yǔ)音的編碼并不是很常用,所以下面主要介紹第一個(gè)包 。

    Base64編碼主要用于Email 傳輸。定義MIME 文檔傳輸?shù)腞FC 規(guī)定了Base 64 編碼,從而使得任何二進(jìn)制數(shù)據(jù)都可以轉(zhuǎn)換成可打印的ASCII字符集安全地傳輸。例如,假設(shè)要通過(guò)Email 傳輸一個(gè)圖形文件,Email 客戶端軟件就會(huì)利用Base64 編碼把圖形文件的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成ASCII 碼。在Base64編碼中,每三個(gè)8 位的字節(jié)被編碼成一個(gè)4 個(gè)字符的組,每個(gè)字符包含原來(lái)24 位中的6 位,編碼后的字符串大小是原來(lái)的1.3倍,文件的末尾追加"="符號(hào)。除了MIME文檔之外,Base64 編碼技術(shù)還用于BASIC認(rèn)證機(jī)制中HTTP 認(rèn)證頭的"用戶:密碼"字符串。

    Base64類(lèi)的使用相當(dāng)簡(jiǎn)單,最主要的兩個(gè)靜態(tài)方法是:Base64.encodeBase64(byte[] byteArray),用于對(duì)字節(jié)數(shù)組中指定的內(nèi)容執(zhí)行Base64 編碼;Base64.decodeBase64(byte[] byteArray),用于對(duì)字節(jié)
    數(shù)組中指定的內(nèi)容執(zhí)行Base64解碼。另外,Base64還有一個(gè)靜態(tài)方法
    Base64.isArrayByteBase64(byte[]
    byteArray),用于檢測(cè)指定的字節(jié)數(shù)組是否可通過(guò)Base64 測(cè)試(即是否包含了經(jīng)過(guò)Base64編碼的數(shù)據(jù),
    如前所述,Base64 編碼的結(jié)果只包含可打印的ASCII字符)。
    byte[] encodedBytes=Base64.encodeBase64(testString.getBytes());
    String decodedString=new String(Base64.decodeBase64(encodedBytes));
    System.err.println("\'^\'是一個(gè)合法的Base64 字符嗎?"
    + Base64.isArrayByteBase64(invalidBytes));
    Hex 編碼/解碼就是執(zhí)行字節(jié)數(shù)據(jù)和等價(jià)的十六進(jìn)制表示形式之間的轉(zhuǎn)換。Hex 編碼的編碼、解碼過(guò)程和Base64 相似,此處不再贅述。

    1.2 Modeler
    ■ 概況:根據(jù)JMX(Java Management Extensions)規(guī)范的定義,支持對(duì)Model MBean(Managed Bean)
    的配置和實(shí)例化。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你想要?jiǎng)?chuàng)建和管理Model MBean,以便利用標(biāo)準(zhǔn)的管理API來(lái)管理應(yīng)用之時(shí)。
    ■ 示例應(yīng)用:ModelerDemo.java,DemoManagedBean.java和mbeans-descriptors.xml。要求
    CLASSPATH 中包含commons-modeler-1.0.jar、commons-logging.jar、
    commons-digester.jar、
    commons-collections.jar、commons-beanutils.jar,以及Sun的JMX參考實(shí)現(xiàn)jmxri.jar。
    ■ 說(shuō)明:
    下面的說(shuō)明要求讀者對(duì)JMX 有一定的了解。
    Managed Bean 簡(jiǎn)稱(chēng)MBean,是一種關(guān)聯(lián)到應(yīng)用程序中被管理組件的Bean,是一種對(duì)資源抽象。Model MBean 是一種特殊的MBean,具有高度動(dòng)態(tài)和可配置的特點(diǎn),但Model MBean 的這種能力是有代價(jià)的,

    程序員需要設(shè)置大量的元信息來(lái)告訴JMX如何創(chuàng)建Model MBean,這些元信息包括組件的屬性、操作和其它信息。Modeler 的目的就是降低程序員實(shí)現(xiàn)Model MBean 的工作量,它提供的一組函數(shù)為處理元數(shù)據(jù)信息帶來(lái)了方便。另外,Modeler還提供了注冊(cè)工具和一個(gè)基本的Model MBean。

    Modeler 允許以XML文件的形式定義元數(shù)據(jù)信息,該XML文件應(yīng)當(dāng)遵從隨同Modeler 提供的DTD 定義。元數(shù)據(jù)信息用來(lái)在運(yùn)行時(shí)創(chuàng)建注冊(cè)信息,注冊(cè)信息是所有Model MBean 的中心知識(shí)庫(kù),實(shí)際上相當(dāng)于一個(gè)創(chuàng)建這類(lèi)Bean 的工廠。

    下面我們首先為一個(gè)Managed Bean(DemoManagedBean)創(chuàng)建這個(gè)XML文件。DemoManagedBean有一
    個(gè)name 屬性,可讀寫(xiě)。
    <?xml version="1.0" encoding="GB2312" ?>
    <!DOCTYPE mbeans-descriptors PUBLIC
    "-//Apache Software Foundation
    //DTD Model MBeans Configuration File"
    "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
    <!-- JMX MBean 的描述-->
    <mbeans-descriptors>
    <mbean name="ManagedBean" description="Example Managed Bean"
    type="ManagedBean">
    <attribute name="name" description="Simple Name"
    type="java.lang.String" />
    <constructor name="ManagedBean"/>
    </mbean>
    </mbeans-descriptors>
    可以看到,這個(gè)XML 文件提供了許多ManagedBean 的信息,包括它的屬性、構(gòu)造函數(shù),另外還有它的操作(不過(guò)本例沒(méi)有顯示),這就是所謂的元數(shù)據(jù)信息。如果你打算擴(kuò)展隨同Modeler 提供的標(biāo)準(zhǔn)MBean(稱(chēng)為BaseModelMBean),可以在mbean元素中以屬性的形式指定Model MBean的類(lèi)名稱(chēng):。在前面的例子中,標(biāo)準(zhǔn)的Model MBean只是簡(jiǎn)單地把所有調(diào)用直接傳遞給ManagedBean 類(lèi)。接下來(lái),我們要注冊(cè)上述信息。注意通過(guò)描述文件裝入注冊(cè)信息之后,我們通過(guò)一個(gè)靜態(tài)方法提取格式化的注冊(cè)信息:
    // 創(chuàng)建一個(gè)Registry
    Registry registry = null;
    try {
    URL url = ModelerDemo.class.getResource
    ("mbeans-descriptors.xml");
    InputStream stream = url.openStream();
    Registry.loadRegistry(stream);
    stream.close();
    registry = Registry.getRegistry();
    } catch (Throwable t) {
    t.printStackTrace(System.out);
    System.exit(1);
    }
    創(chuàng)建好Registry之后,我們要?jiǎng)?chuàng)建一個(gè)Model MBean,并將它注冊(cè)到默認(rèn)的管理服務(wù)器。這樣,任何JMX 客戶程序都可以通過(guò)Model MBean 調(diào)用Managed Bean 的功能了。
    // 獲得一個(gè)Managed Bean 實(shí)例的句柄
    DemoManagedBean mBean = new DemoManagedBean();
    // 創(chuàng)建一個(gè)Model MBean,并將它注冊(cè)到MBean服務(wù)器
    MBeanServer mServer = registry.getServer();
    ManagedBean managed = registry.findManagedBean("ManagedBean");
    try {
    ModelMBean modelMBean = managed.createMBean(mBean);
    String domain = mServer.getDefaultDomain();
    ObjectName oName = new ObjectName(domain +
    ":type=ManagedBean");
    mServer.registerMBean(modelMBean, oName);
    } catch(Exception e) {
    System.err.println(e);
    System.exit(0);
    }
    try {
    ObjectName name =
    new ObjectName(mServer.getDefaultDomain() +
    ":type=ManagedBean");
    ModelMBeanInfo info = (ModelMBeanInfo) mServer.
    getMBeanInfo(name);
    System.err.println(" className="+info.getClassName());
    System.err.println(" description="+info.getDescription());
    System.err.println(" mbeanDescriptor="+info.getMBeanDescriptor());
    System.err.println("==== 測(cè)試====");
    System.err.println("Name 的原始值: " +
    mServer.getAttribute(name, "name"));
    mServer.setAttribute(name, new Attribute("name", "Vikram"));
    System.err.println("Name 的新值: " +
    mServer.getAttribute(name, "name"));
    } catch(Exception e) {
    System.err.println(e);
    System.exit(0);
    }
    雖然這個(gè)例子比較簡(jiǎn)單,但它仍舊清楚地說(shuō)明了使用Modeler帶來(lái)的方便,不妨將它與不使用Modeler的情況下創(chuàng)建一個(gè)類(lèi)似的Model MBean相比較。通過(guò)XML文件來(lái)描述ModelMBeanInfo不僅靈活方便,而且也很容易擴(kuò)展,比手工編寫(xiě)這類(lèi)信息改進(jìn)不少。


    二、XML類(lèi)
    XML 類(lèi)包含了與Java、XML技術(shù)相關(guān)的類(lèi),包括:Betwixt,Digester,Jelly,和JXPath。
    2.1 Betwixt
    ■ 概況:實(shí)現(xiàn)XML 和JavaBean 的映射。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你想要以靈活的方式實(shí)現(xiàn)XML和Bean 的映射,需要一個(gè)數(shù)據(jù)綁定框架之時(shí)。
    ■示例應(yīng)用:BetwixtDemo.java,Mortgage.java,mortgage.xml。要求CLASSPATH 中必須包含
    commons-betwixt-1.0-alpha-1.jar、commons-logging.jar、commons-beanutils.jar、
    commons-collections.jar、以及commons-digester.jar。
    ■ 說(shuō)明:
    如果你以前曾經(jīng)用Castor綁定數(shù)據(jù),一定會(huì)欣賞Betwixt的靈活性。Castor 適合在一個(gè)預(yù)定義模式(Schema)的基礎(chǔ)上執(zhí)行Bean和XML 之間的轉(zhuǎn)換;但如果你只想執(zhí)行數(shù)據(jù)和XML之間的轉(zhuǎn)換,最好的選擇就是Betwixt。Betwixt 的特點(diǎn)就是靈活,能夠方便地將數(shù)據(jù)輸出成為人類(lèi)可閱讀的XML。

    Betwixt的用法相當(dāng)簡(jiǎn)單。如果要把Bean 轉(zhuǎn)換成XML,首先創(chuàng)建一個(gè)BeanWriter 的實(shí)例,設(shè)置其屬性,然后輸出;如果要把XML 轉(zhuǎn)換成Bean,首先創(chuàng)建一個(gè)BeanReader的實(shí)例,設(shè)置其屬性,然后用Digester執(zhí)行轉(zhuǎn)換。
    將Bean轉(zhuǎn)換成XML:
    // 用Betwixt 將Bean轉(zhuǎn)換成XML 必須有BeanWriter的實(shí)例。
    // 由于BeanWriter的構(gòu)造函數(shù)要求有一個(gè)寫(xiě)入器對(duì)象,
    // 所以我們從創(chuàng)建一個(gè)StringWriter開(kāi)始
    StringWriter outputWriter = new StringWriter();
    // 注意輸出結(jié)果并不是格式良好的,所以需要在開(kāi)始位置
    // 寫(xiě)入下面的內(nèi)容:
    outputWriter.write("<?xml version='1.0' ?>");
    // 創(chuàng)建一個(gè)BeanWriter
    BeanWriter writer = new BeanWriter(outputWriter);
    // 我們可以設(shè)置該寫(xiě)入器的各種屬性。
    // 下面的第一行禁止寫(xiě)入ID,
    // 第二行允許格式化輸出
    writer.setWriteIDs(false);
    writer.enablePrettyPrint();
    // 創(chuàng)建一個(gè)Bean 并將其輸出
    Mortgage mortgage = new Mortgage(6.5f, 25);
    // 將輸出結(jié)果寫(xiě)入輸出設(shè)備
    try {
    writer.write("mortgage", mortgage);
    System.err.println(outputWriter.toString());
    } catch(Exception e) {
    System.err.println(e);
    }
    將XML 轉(zhuǎn)換成Bean:
    // 用Betwixt 來(lái)讀取XML 數(shù)據(jù)并以此為基礎(chǔ)創(chuàng)建
    // Bean,必須用到BeanReader 類(lèi)。注意BeanReader 類(lèi)擴(kuò)展了
    // Digester包的Digester 類(lèi)。
    BeanReader reader = new BeanReader();
    // 注冊(cè)類(lèi)
    try {
    reader.registerBeanClass(Mortgage.class);
    // 并解析它…
    Mortgage mortgageConverted =
    (Mortgage)reader.parse(new File("mortgage.xml"));
    // 檢查轉(zhuǎn)換得到的mortgage 是否包含文件中的值
    System.err.println("Rate: " + mortgageConverted.getRate() +
    ", Years: " + mortgageConverted.getYears());
    } catch(Exception ee) {
    ee.printStackTrace();
    }
    注意,通過(guò)BeanReader 注冊(cè)類(lèi)時(shí),如果頂層元素的名稱(chēng)和類(lèi)的名稱(chēng)不同,必須用另一個(gè)方法注冊(cè)并
    指定準(zhǔn)確的路徑,如reader.registerBeanClass("toplevelelementname", Mortgage.class)。


    2.2 Digester
    ■ 概況:提供友好的、事件驅(qū)動(dòng)的高級(jí)XML 文檔處理API。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你想要處理XML 文檔,而且希望能夠根據(jù)XML 文檔中特定的模式所觸發(fā)的一組規(guī)則來(lái)執(zhí)行某些操作時(shí)。
    ■ 示例應(yīng)用:DigesterDemo.java、Employee.java、Company.java、rules.xml以及company.xml。
    要求CLASSPATH 中必須包含commons-digester.jar、commons-logging.jar、
    commons-beanutils.jar
    以及commons-collections.jar。
    ■ 說(shuō)明:
    Digester 在解析配置文件的時(shí)候最為有用。實(shí)際上,Digester最初就是為讀取Struts 配置文件而開(kāi)發(fā)的,后來(lái)才移到Commons 包。
    Digester是一個(gè)強(qiáng)大的模式匹配工具,允許開(kāi)發(fā)者在一個(gè)比SAX 或DOM API更高的層次上處理XML文檔,當(dāng)找到特定的模式(或找不到模式)時(shí)能夠觸發(fā)一組規(guī)則。使用Digester 的基本思路是:

    首先創(chuàng)建一個(gè)Digester 的實(shí)例,然后用它注冊(cè)一系列模式和規(guī)則,最后將XML文檔傳遞給它。此后,Digester就會(huì)分析XML 文檔,按照注冊(cè)次序來(lái)觸發(fā)規(guī)則。如果XML文檔中的某個(gè)元素匹配一條以上的規(guī)則,所有的規(guī)則會(huì)按照注冊(cè)次序被依次觸發(fā)。
    Digester本身帶有12條預(yù)定義的規(guī)則。當(dāng)XML文檔中找到一個(gè)特定的模式時(shí),想要調(diào)用某個(gè)方法嗎?很簡(jiǎn)單,使用預(yù)定義的CallMethodRule!另外,你不一定要使用預(yù)定的規(guī)則,Digester 允許用戶通過(guò)擴(kuò)展Rule 類(lèi)定義自己的規(guī)則。
    在指定模式時(shí),元素必須用絕對(duì)名稱(chēng)給出。例如,根元素直接用名稱(chēng)指定,下一層元素則通過(guò)"/"符號(hào)引出。例如,假設(shè)company是根元素,company/employee 就是匹配其中一個(gè)子元素的模式。

    Digester允許使用通配符,例如*/employee 將匹配XML 文檔內(nèi)出現(xiàn)的所有employee元素。找到匹配的模式時(shí),關(guān)聯(lián)到該匹配模式的規(guī)則內(nèi)有四個(gè)回調(diào)方法會(huì)被調(diào)用,它們是:begin,end,body,和finish。這些方法被調(diào)用的時(shí)刻正如其名字所示,例如調(diào)用begin 和end 的時(shí)刻分別是遇到元素的開(kāi)始標(biāo)記和結(jié)束標(biāo)記之時(shí),body是在遇到了匹配模式之內(nèi)的文本時(shí)被調(diào)用,finish 則是在全部對(duì)匹配模式的處理工作結(jié)束后被調(diào)用。

    最后,模式可以在一個(gè)外部的規(guī)則XML 文檔內(nèi)指定(利用digester-rules.dtd),或者在代碼之內(nèi)指定,下面要使用的是第一種辦法,因?yàn)檫@種辦法比較常用。
    使用Digester 之前要?jiǎng)?chuàng)建兩個(gè)XML文檔。第一個(gè)就是數(shù)據(jù)或配置文件,也就是我們準(zhǔn)備對(duì)其應(yīng)用規(guī)則的文件。下面是一個(gè)例子(company.xml)
    <?xml version="1.0" encoding="gb2312"?>
    <company>
    <name>我的公司</name>
    <address>中國(guó)浙江</address>
    <employee>
    <name>孫悟空</name>
    <employeeNo>10000</employeeNo>
    </employee>
    <employee>
    <name>豬八戒</name>
    <employeeNo>10001</employeeNo>
    </employee>
    </company>
    第二個(gè)文件是規(guī)則文件rules.xml。rules.xml 告訴Digester要在company.xml中查找什么、找到了
    之后執(zhí)行哪些操作:
    <?xml version="1.0" encoding="gb2312"?>
    <digester-rules>
    <!-- 創(chuàng)建頂層的Company對(duì)象-->
    <object-create-rule pattern="company" classname="Company" />
    <call-method-rule pattern="company/name" methodname="setName"
    paramcount="0" />
    <call-method-rule pattern="company/address"
    methodname="setAddress" paramcount="0" />
    <pattern value="company/employee">
    <object-create-rule classname="Employee" />
    <call-method-rule pattern="name" methodname="setName"
    paramcount="0" />
    <call-method-rule pattern="employeeNo" methodname=
    "setEmployeeNo" paramcount="0" />
    <set-next-rule methodname="addEmployee" />
    </pattern>
    </digester-rules>
    這個(gè)文件有哪些含義呢?第一條規(guī)則,<object-create-rule pattern="company"
    classname="Company" />,告訴Digester 如果遇到了模式company,則必須遵從object-create-rule,
    也就是要?jiǎng)?chuàng)建一個(gè)類(lèi)的實(shí)例!那么要?jiǎng)?chuàng)建的是哪一個(gè)類(lèi)的實(shí)例呢?classname="Company"屬性指定了類(lèi)
    的名稱(chēng)。因此,解析company.xml 的時(shí)候,當(dāng)遇到頂級(jí)的company元素,等到object-create-rule規(guī)則執(zhí)
    行完畢,我們就擁有了一個(gè)Digester 創(chuàng)建的Company 類(lèi)的實(shí)例。
    現(xiàn)在要理解call-method-rule 規(guī)則也應(yīng)該不那么困難了,這里call-method-rule 的功能是在遇到
    company/name 或company/address 模式時(shí)調(diào)用一個(gè)方法(方法的名字通過(guò)methodname 屬性指定)。
    最后一個(gè)模式匹配值得注意,它把規(guī)則嵌套到了匹配模式之中。兩種設(shè)定規(guī)則和模式的方式都是
    Digester 接受的,我們可以根據(jù)自己的需要任意選擇。在這個(gè)例子中,模式里面定義的規(guī)則在遇到
    company/employee 模式時(shí)創(chuàng)建一個(gè)Employee 類(lèi)的對(duì)象,設(shè)置其屬性,最后用set-next-rule將這個(gè)雇員
    加入到頂層的Company。
    創(chuàng)建好上面兩個(gè)XML 文件之后,只要用兩行代碼就可以調(diào)用Digester了:
    Digester digester = DigesterLoader.createDigester(rules.toURL());
    Company company = (Company)digester.parse(inputXMLFile);
    第一行代碼裝入規(guī)則文件,創(chuàng)建一個(gè)Digester。第二行代碼利用該Digester 來(lái)應(yīng)用規(guī)則。請(qǐng)參見(jiàn)本文
    后面提供的DigesterDemo.java 完整源代碼。


    2.3 Jelly
    ■ 概況:一種基于Java和XML 的腳本語(yǔ)言。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:簡(jiǎn)單地說(shuō),當(dāng)你想要一種靈活的、可擴(kuò)展的XML 腳本工具之時(shí)。
    ■ 示例應(yīng)用:JellyDemo.java,jellydemo.xml以及TrivialTag.java。要求CLASSPATH 中必須有
    commons-jelly-1.0-dev.jar、dom4j.jar、commons-logging.jar、commons-beanutils.jar以及
    commons-collections.jar。
    ■ 說(shuō)明:
    要說(shuō)清楚Jelly到底是什么以及它扮演著哪種角色是件很不容易的事情。Jelly 試圖提供一個(gè)通用的XML 腳本引擎,這種腳本引擎是可以由開(kāi)發(fā)者通過(guò)定制動(dòng)作和標(biāo)記擴(kuò)展的,XML文檔之中的元素映射到JavaBean,而XML 元素的屬性映射到JavaBean的屬性。從某種意義上說(shuō),Jelly是一種結(jié)合了Betwixt和Digester的工具,但Jelly更強(qiáng)大,具有更好的可擴(kuò)展性。一個(gè)Jelly 系統(tǒng)由多個(gè)組件構(gòu)成。第一個(gè)組件是Jelly 腳本,它是一種由Jelly引擎解析的XML文檔,經(jīng)過(guò)解析的XML 文檔元素被綁定到Jelly 標(biāo)記動(dòng)態(tài)處理。第二個(gè)組件是Jelly標(biāo)記,它是一種實(shí)現(xiàn)了Jelly的Tag 接口的JavaBean,凡是Jelly 標(biāo)記都可以實(shí)現(xiàn)doTag 方法,這個(gè)doTag 方法就是當(dāng)腳本引擎遇到XML 文檔中的特定元素時(shí)所執(zhí)行的方法。Jelly正是通過(guò)這一機(jī)制實(shí)現(xiàn)動(dòng)態(tài)的腳本處理能力,從某種意義上看,有點(diǎn)類(lèi)似于Digester 的工作機(jī)制。

    Jelly 帶有許多預(yù)定義的標(biāo)記,其中部分標(biāo)記提供核心Jelly支持,其他標(biāo)記用來(lái)提供解析、循環(huán)、條件執(zhí)行代碼等方面的支持。另外,Jelly 還為Ant任務(wù)提供了廣泛的支持。要在Java應(yīng)用程序中使用Jelly,首先要?jiǎng)?chuàng)建一個(gè)JellyContext的實(shí)例,例如:JellyContext context
    = new JellyContext();。我們可以把JellyContext對(duì)象看成是一個(gè)編譯和運(yùn)行Jelly腳本的運(yùn)行環(huán)境。
    有了JellyContext 就可以運(yùn)行Jelly 腳本。JellyContext的輸出實(shí)際上是一個(gè)XMLOutput類(lèi)的實(shí)例:
    context.runScript(new File("jellydemo.xml"), output);。
    創(chuàng)建自定義標(biāo)記時(shí),我們既可以覆蓋上面提到的doTag方法(如下面的例子所示),或者提供一個(gè)執(zhí)
    行方法,如invoke()或run():
    public void doTag(XMLOutput output) throws Exception {
    // 在這里加入要執(zhí)行的操作,
    // 例如設(shè)置屬性、訪問(wèn)文件系統(tǒng)等…
    this.intProp = 3;
    }
    下面提供了一個(gè)定義Jelly 腳本的XML 文件示例:
    <j:jelly xmlns:j="jelly:core" xmlns:define="jelly:define"
    xmlns:tr="trivialTag">
    <define:taglib uri="trivialTag">
    <define:jellybean name="trivial" className="TrivialTag" />
    </define:taglib>
    <tr:trivial intProp="1" stringProp="ball">Hello World</tr:trivial>
    </j:jelly>
    這個(gè)例子用到j(luò)elly:define 和jelly:core標(biāo)記,以及一個(gè)trivialTag 標(biāo)記。當(dāng)遇到trivial標(biāo)記實(shí)例時(shí),Jelly創(chuàng)建相應(yīng)的JavaBean 的實(shí)例,執(zhí)行doTag 方法(或者也可以是一個(gè)run 或invoke之類(lèi)可調(diào)用的方法)。Jelly 還有許多其他功能,它既可以直接從命令行或Ant腳本運(yùn)行,也可以嵌入到應(yīng)用程序的代碼之內(nèi),請(qǐng)參見(jiàn)Jelly 文檔了解詳情。


    2.4 JXPath
    ■ 概況:Java中的XPath 解釋器。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你想要在JavaBean、DOM或其他對(duì)象構(gòu)成的結(jié)構(gòu)中應(yīng)用XPath 查詢之時(shí)。
    ■ 示例應(yīng)用:JXPathDemo.java,Book.java,Author.java。要求CLASSPATH必須包含
    commons-jxpath-1.1.jar。
    ■ 說(shuō)明:
    下面的說(shuō)明要求讀者已具備基本的XPath 知識(shí)。
    XPath是一種查詢XML文檔的語(yǔ)言,JXPath將同一概念應(yīng)用到了其他Java對(duì)象的查詢,諸如JavaBean、
    Collection、Array和Map 等。
    JXPathContext是JXPath中的核心類(lèi),它利用一個(gè)工廠方法來(lái)定位和創(chuàng)建一個(gè)上下文的實(shí)例。由于
    有了這一機(jī)制,必要時(shí)開(kāi)發(fā)者可以插入一個(gè)新的JXPath 的實(shí)現(xiàn)。要使用JXPathContext,只要簡(jiǎn)單地向
    它傳遞一個(gè)JavaBean、Collection 或Map,例如:JXPathContext context =
    JXPathContext.newContext(book);。
    利用JXPathContext 可執(zhí)行許多任務(wù)。例如訪問(wèn)屬性或嵌套屬性,當(dāng)然還可以設(shè)置屬性:
    System.err.println(context.getValue("title"));
    System.err.println(context.getValue("author/authorId"));
    context.setValue("author/authorId", "1001");
    利用JXPath 還可以查找其他類(lèi)型的對(duì)象,不過(guò)創(chuàng)建上下文對(duì)象的方式都一樣,都是用上面介紹的靜態(tài)方法獲得一個(gè)新的上下文,傳入想要查詢的對(duì)象。



    結(jié)束語(yǔ):有關(guān)包裝類(lèi)和XML 類(lèi)的介紹就到這里結(jié)束。在下一篇也是最后一篇文章中,我們將了解工具類(lèi)的包。在這個(gè)系列文章的第一篇中,我們把Commons項(xiàng)目包含的組件分成了5類(lèi),介紹了Web類(lèi)和其他類(lèi)。第二篇文章論及XML 類(lèi)和包裝類(lèi)。這是最后一篇,探討工具類(lèi)的組件。注意Commons本身并不進(jìn)行這
    種分類(lèi),這里進(jìn)行分類(lèi)純粹是為說(shuō)明和組織方便起見(jiàn)。



    第三部分、工具類(lèi)
    工具類(lèi)包含BeanUtils、Logging、DBCP、Pool和Validator 這幾個(gè)組件。
    一、BeanUtils
    ■ 概況:提供了動(dòng)態(tài)操作JavaBean 的工具。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你需要?jiǎng)討B(tài)訪問(wèn)JavaBean,但對(duì)已編譯好的accessor 和
    modifier 一無(wú)所知之時(shí)。被動(dòng)態(tài)訪問(wèn)的JavaBean 必須遵從JavaBeans
    specification 定義的命名設(shè)計(jì)規(guī)范。
    ■ 示例應(yīng)用:BeanUtilsDemo.java,AppLayer1Bean.java,
    AppLayer2Bean.java,SubBean.java。要求CLASSPATH 中必須包含commons-beanutils.jar、
    commons-logging.jar 以及commons-collections.jar。
    ■ 說(shuō)明:
    在動(dòng)態(tài)Java應(yīng)用程序設(shè)計(jì)環(huán)境中,我們不一定能夠預(yù)先獲知JavaBean 的各種set、get 方法。即使已經(jīng)
    知道了這些方法的名字,為Bean 的每個(gè)屬性依次寫(xiě)出setXXX 或getXXX方法也是一件很麻煩的事情。考
    慮一下這種情形:幾個(gè)幾乎完全相同的Bean 從應(yīng)用的一個(gè)層傳遞到另一個(gè)層,你會(huì)為每一個(gè)屬性調(diào)用
    bean1.setXXX(bean2.getXXX())嗎?雖然你可以這么做,但并非一定得這么做,因?yàn)槟憧梢宰孊eanUtils
    為你完成這些繁瑣的操作!BeanUtils可以幫助開(kāi)發(fā)者動(dòng)態(tài)地創(chuàng)建、修改和復(fù)制JavaBean。
    BeanUtils 能夠操作符合下列條件的JavaBean:
    ⑴ JavaBean必須提供一個(gè)沒(méi)有參數(shù)的構(gòu)造函數(shù)。
    ⑵ JavaBean的屬性必須能夠通過(guò)getXXX和setXXX方法訪問(wèn)和修改。對(duì)于Boolean屬性,也允許使用isXXX
    和setXXX。JavaBean的屬性可以是只讀或只寫(xiě)的,也就是說(shuō),允許只提供屬性的set或get方法。
    ⑶ 如果不采用傳統(tǒng)的命名方式(即用get 和set),改用其它方式命名JavaBean 的accessor和modifier


    那么必須通過(guò)與JavaBean 關(guān)聯(lián)的BeanInfo 類(lèi)聲明這一點(diǎn)。
    下面來(lái)看一個(gè)簡(jiǎn)單的例子。
    要獲取和設(shè)置JavaBean 的簡(jiǎn)單屬性,分別使用PropertyUtils.
    getSimpleProperty(Object bean, String name)以及PropertyUtils.
    setSimpleProperty(Object bean, String name, Object value)方法。如下面的例子所示,其中
    AppLayer1Bean.java和AppLayer2Bean.java 定義了兩個(gè)測(cè)試用的JavaBean。
    PropertyUtils.setSimpleProperty(app1Bean,"intProp1", new Integer(10));
    System.err.println("App1LayerBean, stringProp1: " +
    PropertyUtils.getSimpleProperty(app1Bean, "stringProp1"));
    既然我們可以通過(guò)直接調(diào)用Bean 的方法(app1Bean.getStringProp1()或
    app1Bean.setIntProp1(10))來(lái)獲取或設(shè)置Bean 的屬性,為什么還要使用setSimpleProperty、
    getSimpleProperty方法呢?這是因?yàn)椋覀儾灰欢軌蝾A(yù)先知道JavaBean 屬性的名字,因此也不一定
    知道要調(diào)用哪些方法才能獲取/設(shè)置對(duì)應(yīng)的屬性。這些屬性的名字可能來(lái)自其他過(guò)程或外部應(yīng)用程序設(shè)置
    的變量。因此,一旦搞清楚了JavaBean的屬性的名字并把它保存到一個(gè)變量,你就可以將變量傳遞給
    PropertyUtils,再也不必依靠其他開(kāi)發(fā)者才能預(yù)先得知正確的方法名字。
    那么,如果JavaBean 的屬性不是簡(jiǎn)單數(shù)據(jù)類(lèi)型,又該怎么辦呢?例如,JavaBean的屬性可能是一個(gè)
    Collection,也可能是一個(gè)Map。在這種情況下,我們要改用PropertyUtils.getIndexedProperty或
    PropertyUtils.getMappedProperty。對(duì)于集合類(lèi)屬性值,我們必須指定一個(gè)索引值,規(guī)定待提取或設(shè)置
    的值在集合中的位置;對(duì)于Map 類(lèi)屬性,我們必須指定一個(gè)鍵,表示要提取的是哪一個(gè)值。下面是兩個(gè)例

    子:
    PropertyUtils.setIndexedProperty(
    app1Bean, "listProp1[1]", "新字符串1");
    System.err.println("App1LayerBean, listProp1[1]: " +
    PropertyUtils.getIndexedProperty(app1Bean,
    "listProp1[1]"));
    請(qǐng)注意,對(duì)于可索引的屬性,索引值是通過(guò)方括號(hào)傳遞的。例如上面的例子中,我們把JavaBean
    (app1Bean)的List中索引為1 的值設(shè)置成了"新字符串1",后面的一行代碼又從索引1的位置提取同
    一個(gè)值。還有另一種方式也可以達(dá)到同樣的目標(biāo),即使用
    PropertyUtils.setIndexedProperty(Object
    bean, String name, int index, Object value)和PropertyUtils.getIndexedProperty(Object bean,
    String name, int index)方法,在這兩個(gè)方法中索引值作為方法的參數(shù)傳遞。對(duì)于Map類(lèi)屬性,也有類(lèi)
    似的方法,只要改用鍵(而不是索引)來(lái)獲取或設(shè)置指定的值。
    最后,Bean的屬性可能也是一個(gè)Bean。那么,怎樣來(lái)獲取或設(shè)置那些以屬性的形式從屬于主Bean 的
    屬性Bean 呢?只要使用PropertyUtils.getNestedProperty(Object bean, String name)和
    PropertyUtils.setNestedProperty(Object bean, String name, Object value)方法就可以了。下面提
    供了一個(gè)例子。
    // 訪問(wèn)和設(shè)置嵌套的屬性
    PropertyUtils.setNestedProperty(app1Bean, "subBean.stringProp",
    "來(lái)自SubBean 的信息,通過(guò)setNestedProperty 設(shè)置。");
    System.err.println(
    PropertyUtils.getNestedProperty(app1Bean,"subBean.stringProp"));
    通過(guò)上面的例子可以看出,從屬Bean 的屬性是通過(guò)一個(gè)句點(diǎn)符號(hào)訪問(wèn)的。
    上述幾種訪問(wèn)屬性的方式可以結(jié)合在一起使用,嵌套深度不受限制。具體要用到的兩個(gè)方法是
    PropertyUtils.getProperty(Object bean, String name)和PropertyUtils.setProperty(Object bean,
    String name, Object value)。例如:PropertyUtils.setProperty(app1Bean, "subBean.listProp[0]",
    "屬性的值");。
    這個(gè)例子是把嵌套Bean 對(duì)象和可索引屬性結(jié)合在一起訪問(wèn)。
    BeanUtils經(jīng)常用于動(dòng)態(tài)訪問(wèn)Web 應(yīng)用中的請(qǐng)求參數(shù)。實(shí)際上,正是BeanUtils觸發(fā)了Struts 項(xiàng)目中
    把請(qǐng)求參數(shù)動(dòng)態(tài)轉(zhuǎn)換成系統(tǒng)JavaBean 的靈感:利用代碼把用戶填寫(xiě)的表單轉(zhuǎn)換成一個(gè)Map,其中參數(shù)的
    名字變成Map中的鍵,參數(shù)的值則來(lái)自于用戶在表單中輸入的數(shù)據(jù),然后由一個(gè)簡(jiǎn)單的
    BeanUtils.populate調(diào)用把這些值轉(zhuǎn)換成一個(gè)系統(tǒng)Bean。
    最后,BeanUtils 提供了一個(gè)一步到位的方法把數(shù)據(jù)從一個(gè)Bean 復(fù)制到另一個(gè)Bean:
    // 把a(bǔ)pp1Bean 的數(shù)據(jù)復(fù)制到app2Bean
    BeanUtils.copyProperties(app2Bean, app1Bean);
    BeanUtils 還有一些這里尚未提及的實(shí)用方法。不過(guò)不必?fù)?dān)心,BeanUtils 是Commons 中文檔較為完善的
    組件之一,建議讀者參閱BeanUtils 包的JavaDoc 文檔了解其余方法的相關(guān)信息。



    二、Logging
    ■ 概況:一個(gè)封裝了許多流行日志工具的代碼庫(kù),并提供統(tǒng)一的日志訪問(wèn)接口。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你的應(yīng)用需要一種以上的日志工具之時(shí),或者預(yù)期以后會(huì)有這種需要之時(shí)。
    ■ 示例應(yīng)用:LoggingDemo.java,commons-logging.properties。要求CLASSPATH中必須包含
    commons-logging.jar,有時(shí)還需要log4j.jar。
    ■ 說(shuō)明:
    日志(Logging)使得我們能夠調(diào)試和跟蹤應(yīng)用程序任意時(shí)刻的行為和狀態(tài)。在任何規(guī)模較大的應(yīng)用中,
    Logging 都是不可或缺的組成部分,因此現(xiàn)在已經(jīng)有許多第三方Logging工具,它們免去了開(kāi)發(fā)者自己編
    寫(xiě)Logging API 之勞。實(shí)際上,即使JDK 也帶有構(gòu)造好了的Logging API。既然已經(jīng)有這么多選擇(log4j


    JDK,Logkit,等等),通常我們總是可以找到最適合自己應(yīng)用要求的現(xiàn)成API。
    不過(guò)也有可能出現(xiàn)例外的情形,例如一個(gè)熟悉的Logging API 不能和當(dāng)前的應(yīng)用程序兼容,或者是由于某
    種硬性規(guī)定,或者是由于應(yīng)用的體系結(jié)構(gòu)方面的原因。Commons項(xiàng)目Logging 組件的辦法是將記錄日志的
    功能封裝為一組標(biāo)準(zhǔn)的API,但其底層實(shí)現(xiàn)卻可以任意修改和變換。開(kāi)發(fā)者利用這個(gè)API來(lái)執(zhí)行記錄日志
    信息的命令,由API 來(lái)決定把這些命令傳遞給適當(dāng)?shù)牡讓泳浔R虼耍瑢?duì)于開(kāi)發(fā)者來(lái)說(shuō),Logging組件對(duì)
    于任何具體的底層實(shí)現(xiàn)都是中立的。
    如果你熟悉log4j,使用Commons 的Logging API 應(yīng)該不會(huì)有什么問(wèn)題。即使你不熟悉log4j,只要知道
    使用Logging必須導(dǎo)入兩個(gè)類(lèi)、創(chuàng)建一個(gè)Log 的靜態(tài)實(shí)例,下面顯示了這部分操作的代碼:
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    public class LoggingDemo {
    private static Log log = LogFactory.getLog
    (LoggingDemo.class);
    // ...
    }
    有必要詳細(xì)說(shuō)明一下調(diào)用LogFactory.getLog()時(shí)發(fā)生的事情。調(diào)用該函數(shù)會(huì)啟動(dòng)一個(gè)發(fā)現(xiàn)過(guò)程,即
    找出必需的底層日志記錄功能的實(shí)現(xiàn),具體的發(fā)現(xiàn)過(guò)程在下面列出。注意,不管底層的日志工具是怎么找
    到的,它都必須是一個(gè)實(shí)現(xiàn)了Log 接口的類(lèi),且必須在CLASSPATH 之中。Commons LoggingAPI直接提供
    對(duì)下列底層日志記錄工具的支持:Jdk14Logger,Log4JLogger,LogKitLogger,NoOpLogger(直接丟棄
    所有日志信息),還有一個(gè)SimpleLog。
    ⑴ Commons 的Logging 首先在CLASSPATH 中尋找一個(gè)commons-logging.properties 文件。這個(gè)屬性
    文件至少必須定義org.apache.commons.logging.Log 屬性,它的值應(yīng)該是上述任意Log 接口實(shí)現(xiàn)的完整
    限定名稱(chēng)。
    ⑵ 如果上面的步驟失敗,Commons 的Logging接著檢查系統(tǒng)屬性
    org.apache.commons.logging.Log。
    ⑶ 如果找不到org.apache.commons.logging.Log系統(tǒng)屬性,Logging接著在CLASSPATH中尋找log4j
    的類(lèi)。如果找到了,Logging就假定應(yīng)用要使用的是log4j。不過(guò)這時(shí)log4j 本身的屬性仍要通過(guò)
    log4j.properties 文件正確配置。
    ⑷ 如果上述查找均不能找到適當(dāng)?shù)腖ogging API,但應(yīng)用程序正運(yùn)行在JRE 1.4 或更高版本上,則
    默認(rèn)使用JRE 1.4 的日志記錄功能。
    ⑸ 最后,如果上述操作都失敗,則應(yīng)用將使用內(nèi)建的SimpleLog。SimpleLog 把所有日志信息直接輸
    出到System.err。
    獲得適當(dāng)?shù)牡讓尤罩居涗浌ぞ咧螅酉聛?lái)就可以開(kāi)始記錄日志信息。作為一種標(biāo)準(zhǔn)的API,Commons
    Logging API 主要的好處是在底層日志機(jī)制的基礎(chǔ)上建立了一個(gè)抽象層,通過(guò)抽象層把調(diào)用轉(zhuǎn)換成與具體
    實(shí)現(xiàn)有關(guān)的日志記錄命令。
    本文提供的示例程序會(huì)輸出一個(gè)提示信息,告訴你當(dāng)前正在使用哪一種底層的日志工具。請(qǐng)?jiān)囍诓?
    同的環(huán)境配置下運(yùn)行這個(gè)程序,例如,在不指定任何屬性的情況下運(yùn)行這個(gè)程序,這時(shí)默認(rèn)將使用
    Jdk14Logger;然后指定系統(tǒng)屬性-Jorg.apache.commons.logging.Log=org.apache.commons.logging.imp

    l.SimpleLog
    再運(yùn)行程序,這時(shí)日志記錄工具將是SimpleLog;最后,把Log4J的類(lèi)放入
    CLASSPATH,只要正確設(shè)置了log4j 的log4j.properties 配置文件,就可以得到Log4JLogger輸出的信息


    三、Pool
    ■ 概況:用來(lái)管理對(duì)象池的代碼庫(kù)。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:當(dāng)你需要管理一個(gè)對(duì)象實(shí)例池之時(shí)。
    ■ 示例應(yīng)用:PoolDemo.java 和MyObjectFactory.java。要求CLASSPATH中必須有commons-pool.jar
    和commons-collections.jar。
    ■ 說(shuō)明:
    Pool 組件定義了一組用于對(duì)象池的接口,另外還提供了幾個(gè)通用的對(duì)象池實(shí)現(xiàn),以及
    一些幫助開(kāi)發(fā)者自己創(chuàng)建對(duì)象池的基礎(chǔ)類(lèi)。
    對(duì)于大多數(shù)開(kāi)發(fā)者來(lái)說(shuō),對(duì)象池應(yīng)該不算什么新概念了。也許許多讀者已經(jīng)在訪問(wèn)數(shù)據(jù)
    庫(kù)的時(shí)候使用過(guò)數(shù)據(jù)庫(kù)連接池,對(duì)象池的概念其實(shí)也相似。對(duì)象池允許開(kāi)發(fā)者在緩沖區(qū)中創(chuàng)
    建一組對(duì)象(創(chuàng)建對(duì)象的操作可以通過(guò)應(yīng)用的配置文件完成,或者也可以在應(yīng)用的啟動(dòng)階段
    完成),當(dāng)應(yīng)用程序需要用到對(duì)象時(shí)就可以很快獲得相響應(yīng)。如果應(yīng)用程序不再需要對(duì)象,
    它仍舊把對(duì)象返回給緩沖池,下次需要使用對(duì)象時(shí)再?gòu)木彌_池提取。
    Pool 組件允許我們創(chuàng)建對(duì)象(實(shí)例)池,但不限制我們一定要使用某個(gè)具體的實(shí)現(xiàn)。
    Pool 組件本身提供了幾種實(shí)現(xiàn),必要時(shí)我們還可以創(chuàng)建自己的實(shí)現(xiàn)。
    Pool 組件包含三個(gè)基本的類(lèi):ObjectPool,這是一個(gè)定義和維護(hù)對(duì)象池的接口;
    ObjectPoolFactory,負(fù)責(zé)創(chuàng)建ObjectPool 的實(shí)例;還有一個(gè)PoolableObjectFacotry,它
    為那些用于ObjectPool 之內(nèi)的實(shí)例定義了一組生命周期方法。
    如前面指出的,Pool組件包含幾種通用的實(shí)現(xiàn),其中一個(gè)就是GenericObjectPool,下
    面通過(guò)一個(gè)實(shí)例來(lái)看看它的用法。
    ① 創(chuàng)建一個(gè)PoolableObjectFactory。這個(gè)工廠類(lèi)定義對(duì)象如何被創(chuàng)建、拆除和驗(yàn)證。
    import org.apache.commons.pool.PoolableObjectFactory;
    public class MyObjectFactory implements
    PoolableObjectFactory {
    private static int counter;
    // 返回一個(gè)新的字符串
    public Object makeObject() {
    return String.valueOf(counter++);
    }
    public void destroyObject(Object obj) {}
    public boolean validateObject(Object obj)
    { return true; }
    public void activateObject(Object obj) {}
    public void passivateObject(Object obj) {}
    }
    本例創(chuàng)建了一個(gè)序號(hào)不斷增加的String 對(duì)象的池,驗(yàn)證操作(validateObject)總是
    返回true。
    ② 利用PoolableObjectFactory 創(chuàng)建一個(gè)GenericObjectPool,maxActive、maxIdle
    等選項(xiàng)都采用默認(rèn)值。
    GenericObjectPool pool = new GenericObjectPool
    (new MyObjectFactory());
    ③ 從對(duì)象池"借用"一個(gè)對(duì)象。
    System.err.println("Borrowed: " + pool.borrowObject());
    ④ 把對(duì)象返回給對(duì)象池。
    pool.returnObject("0");
    對(duì)象池的狀態(tài)可以通過(guò)多種方法獲知,例如:
    // 有多少對(duì)象已經(jīng)激活(已被借用)?
    System.err.println("當(dāng)前活動(dòng)的對(duì)象數(shù)量: " + pool.getNumActive());
    本文后面提供的PoolDemo.java 提供了完整的源代碼。



    四、DBCP
    ■ 概況:數(shù)據(jù)庫(kù)連接池。建立在Pool 組件的基礎(chǔ)上。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:需要訪問(wèn)關(guān)系數(shù)據(jù)庫(kù)之時(shí)。
    ■ 示例應(yīng)用:DBCPDemo.java。要求CLASSPATH中必須有commons-dbcp.jar、
    commons-pool.jar 以及commons-collections.jar。另外還要能夠訪問(wèn)數(shù)據(jù)庫(kù),配置適合該數(shù)據(jù)庫(kù)的JDBC 驅(qū)動(dòng)程序。示例應(yīng)用測(cè)試的是一個(gè)MySQL數(shù)據(jù)庫(kù)連接,驅(qū)動(dòng)程序是MySQL JDBCdriver。注意運(yùn)行這個(gè)程序需要二進(jìn)制文件的nightly 版,當(dāng)前的正式發(fā)行版缺少某些必需的類(lèi)。

    最后,運(yùn)行這個(gè)示例程序時(shí),應(yīng)當(dāng)確保已經(jīng)為JDBC 驅(qū)動(dòng)程序設(shè)置了系統(tǒng)屬性(-Djdbc.drivers=com.mysql.jdbc.Driver)。
    ■ 說(shuō)明:
    DBCP 建立在Pool組件的基礎(chǔ)上,提供了數(shù)據(jù)庫(kù)連接緩沖池機(jī)制。與常規(guī)的連接池相比,DBCP 的使用要稍微復(fù)雜一點(diǎn),因?yàn)樗乃悸肥且詡蜫DBC 驅(qū)動(dòng)程序的形式提供一個(gè)通用的體系。不過(guò),前面我們已經(jīng)了解了Pool組件的基本知識(shí),現(xiàn)在要理解DBCP 的用法應(yīng)該也很簡(jiǎn)單了。
    // ...
    // ① 創(chuàng)建一個(gè)GenericObjectPool 類(lèi)的實(shí)例。
    GenericObjectPool pool = new GenericObjectPool(null);
    // ...
    // ② 在前面討論P(yáng)ool 組件時(shí)提到GenericObjectPool
    // 要求有一個(gè)PoolableObjectFactory 來(lái)創(chuàng)建需
    // 要緩沖的Object 的實(shí)例,對(duì)于DBCP 來(lái)說(shuō),
    // 這一功能現(xiàn)在由PoolableConnectionFactory提
    // 供,如下面的例子所示:
    DriverManagerConnectionFactory cf =
    new DriverManagerConnectionFactory(
    "jdbc:mysql://host/db", "username", "password");
    PoolableConnectionFactory pcf = new PoolableConnectionFactory(
    CF, pool, null, "SELECT * FROM mysql.db", false, true);
    // ...
    // ③ 現(xiàn)在,我們只要?jiǎng)?chuàng)建并注冊(cè)PoolingDriver:
    new PoolingDriver().registerPool("myPool", pool);
    接下來(lái)就可以從這個(gè)連接池提取連接了。注意創(chuàng)建這個(gè)連接池時(shí)采用了maxActive、
    maxIdle 等選項(xiàng)的默認(rèn)值,如有必要,你可以在前面步驟1創(chuàng)建GenericObjectPool 類(lèi)的實(shí)例時(shí)自定義這些值。DBCPDemo.java 提供了一個(gè)完整的實(shí)例。




    五、Validator
    ■ 概況:一個(gè)收集了常見(jiàn)用戶輸入驗(yàn)證功能的API。
    ■ 官方資源:主頁(yè),二進(jìn)制,源代碼。
    ■ 何時(shí)適用:對(duì)JavaBean 執(zhí)行常規(guī)驗(yàn)證操作之時(shí)。
    ■ 示例應(yīng)用:ValidatorDemo.java,MyValidator.java,MyFormBean.java,
    validation.xml。要求CLASSPATH 中必須有commons-validator.jar,
    commons-beanutils.jar,commons-collections.jar,commons-digester.jar,以及
    commons-logging.jar。
    ■ 說(shuō)明:
    如果你曾經(jīng)用Struts 開(kāi)發(fā)過(guò)Web 應(yīng)用,那么應(yīng)該已經(jīng)用過(guò)Validator包了。Validator包極大地簡(jiǎn)化了用戶輸入數(shù)據(jù)的檢驗(yàn)。不過(guò),Validator 并不局限于Web應(yīng)用,它還可以方便地用于其它使用了JavaBean的場(chǎng)合。

    Validator 允許為用戶輸入域定義驗(yàn)證條件,支持錯(cuò)誤信息國(guó)際化,允許創(chuàng)建自定義的
    驗(yàn)證器,此外,Validator 包還提供了一些預(yù)定義的可以直接使用的驗(yàn)證器。
    驗(yàn)證規(guī)則和驗(yàn)證方法用XML文件定義(可以用一個(gè)或者多個(gè)XML 文件定義,但通常而言,把它們分開(kāi)比較好)。驗(yàn)證方法文件定義了要用到的驗(yàn)證器,指定各個(gè)實(shí)際實(shí)現(xiàn)驗(yàn)證器的Java 類(lèi)(不要求這些類(lèi)實(shí)現(xiàn)某些特定的接口,也不要求這些類(lèi)必須從特定的類(lèi)派生,只需要遵從方法定義文件中聲明的定義就可以了)。

    下面我們就來(lái)構(gòu)造一個(gè)自定義的驗(yàn)證器,它的功能是檢查Bean的一個(gè)String屬性是否
    包含特定的字符("*")。
    import org.apache.commons.validator.*;
    public class MyValidator {
    public static boolean validateContainsChar(
    Object bean, Field field) {
    // 首先獲得Bean 的屬性(即一個(gè)String 值)
    String val = ValidatorUtil.getValueAsString
    (bean, field.getProperty());
    // 根據(jù)屬性中是否包含"*"字符,返回true 或false。
    return ((val.indexOf('*') == -1)?false:true);
    }
    }
    ValidatorUtil類(lèi)提供了許多實(shí)用方法,例如ValidatorUtil.getValueAsString用來(lái)
    提取Bean的屬性值并返回一個(gè)String。現(xiàn)在我們要在XML文件中聲明MyValidator驗(yàn)證器。
    <!-- 定義驗(yàn)證器方法-->
    <global>
    <validator name="containsStar"
    classname="MyValidator"
    method="validateContainsChar"
    methodParams="java.lang.Object,
    org.apache.commons.validator.Field" />
    </global>
    可以看到,XML 文件詳細(xì)地定義了驗(yàn)證方法的特征,包括該方法的輸入?yún)?shù)。下面來(lái)看
    看使用這個(gè)驗(yàn)證器的步驟。
    ① 在上面的XML文件中加入我們要實(shí)現(xiàn)的驗(yàn)證規(guī)則。
    <!-- 定義驗(yàn)證規(guī)則-->
    <formset>
    <!-- 檢查Bean的name 屬性是否能夠通過(guò)
    containsStar測(cè)試-->
    <form name="myFormBean">
    <field property="name" depends="containsStar">
    <arg0 key="myFormBean.name" />
    </field>
    </form>
    </formset>
    可以看到,所有驗(yàn)證規(guī)則都在formset 元素之內(nèi)聲明。formset 元素之內(nèi)首先聲明要驗(yàn)
    證的表單,表單之內(nèi)列出了要驗(yàn)證的輸入域及其驗(yàn)證條件。在本例中,我們希望驗(yàn)證
    myFormBean的name屬性,檢查該屬性是否能夠通過(guò)containsStar的驗(yàn)證(也即name 屬性
    的值是否包含"*"字符)。
    ② 以XML文件為基礎(chǔ),創(chuàng)建一個(gè)Validator 實(shí)例并予以初始化。
    // 裝入驗(yàn)證器XML 文件
    InputStream in = getClass().getResourceAsStream
    ("validator.xml");
    // 創(chuàng)建一個(gè)ValidatorResources
    ValidatorResources resources = new ValidatorResources();
    // 初始化驗(yàn)證器資源
    ValidatorResourcesInitializer.initialize(resources, in);
    // 創(chuàng)建Validator
    Validator validator = new Validator(resources, "myFormBean");
    validator.addResource(Validator.BEAN_KEY, bean);
    ③ 驗(yàn)證Bean。驗(yàn)證的結(jié)果是一個(gè)ValidatorResults,其中包含了各個(gè)要求驗(yàn)證的屬性
    按照各自的驗(yàn)證條件執(zhí)行驗(yàn)證的結(jié)果。
    // 執(zhí)行驗(yàn)證
    ValidatorResults results = validator.validate();
    ④ 處理ValidationResults。
    //驗(yàn)證結(jié)果對(duì)象ValidationResults 可能還包含了驗(yàn)證其他表單屬性的結(jié)果,
    //對(duì)于每一個(gè)屬性,我們都可以單獨(dú)提取其驗(yàn)證結(jié)果。
    ValidatorResult result = results.getValidatorResult("name");
    // 對(duì)于每一個(gè)屬性,我們可以分別檢查各個(gè)驗(yàn)證條件的檢查結(jié)果。
    // 例如,name 屬性通過(guò)了containsStar 驗(yàn)證嗎?
    System.err.println("name 屬性包含"*"字符的測(cè)試結(jié)果:" +
    result.isValid("containsStar"));

    對(duì)于每一個(gè)ValidationResult 的實(shí)例,我們可以查詢它是否通過(guò)了某項(xiàng)特定的檢查。
    例如,在上面的代碼中,我們用result.isValid('containsStart')表達(dá)式來(lái)檢查name 屬性的ValidatorResult 實(shí)例,看看它是否通過(guò)了containsStar 驗(yàn)證。

    對(duì)于Web 應(yīng)用來(lái)說(shuō),Validator是一個(gè)相當(dāng)有用的組件,它提供了一組預(yù)定義的驗(yàn)證器,極大地方便了用戶輸入合法性的驗(yàn)證。預(yù)定義的驗(yàn)證器可以用來(lái)(但不限于)檢查輸入值的范圍、數(shù)據(jù)類(lèi)型、長(zhǎng)度,以及email 地址和地理位置檢查。此外,我們還可以自己定義驗(yàn)證器并將它加入到Validator框架之中。
    posted on 2008-11-07 10:59 sanwish 閱讀(318) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): java
    主站蜘蛛池模板: 国产精品色午夜视频免费看| 性xxxxx免费视频播放| 久久久久国色av免费看| 久久不见久久见免费视频7| 国产一卡2卡3卡4卡2021免费观看| 成年男女男精品免费视频网站| 国产免费观看黄AV片| 精品国产亚洲一区二区在线观看| 久久精品夜色国产亚洲av| 亚洲乱码中文字幕小综合| 色偷偷噜噜噜亚洲男人| 国产成人高清精品免费观看| 最近2019中文字幕免费大全5| 在线观看免费为成年视频| 久久久久久A亚洲欧洲AV冫| 亚洲精品福利网泷泽萝拉| 亚洲av成人中文无码专区| a级毛片毛片免费观看久潮| 在线看片v免费观看视频777| 免费一级毛片女人图片| 亚洲av福利无码无一区二区| 亚洲欧美乱色情图片| 国产在线精品一区免费香蕉| 亚洲免费网站观看视频| 亚洲美女在线国产| 亚洲另类春色国产精品| 一级做a爱过程免费视频高清| 1a级毛片免费观看| 亚洲国产成人精品91久久久| 亚洲色偷偷av男人的天堂 | 亚洲综合国产精品第一页| 中文字幕亚洲综合久久2| 精品一区二区三区无码免费直播| 日本不卡免费新一区二区三区| 成人毛片免费观看| 久久青草亚洲AV无码麻豆| 春暖花开亚洲性无区一区二区| 三年片在线观看免费观看大全动漫 | 久久午夜夜伦鲁鲁片无码免费| 国产自产拍精品视频免费看| 99久久亚洲综合精品成人网|