Jakarta Commons是Jakarta的子項目,它創建和維護著許多獨立軟件包,這些包一般與其他框架或產品無關,其中收集了大量小型、實用的組件,大部分面向服務器端編程。
Commons的包分成兩部分:Sandbox,Commons代碼庫。Sandbox是一個測試平臺,用來檢驗各種設想、計劃。本文介紹的組件屬于Commons代碼庫,文章將展示各個組件的功能、適用場合,并通過簡單的例子介紹其用法。
一、概述
可重用性是Jakarta Commons項目的靈魂所在。這些包在設計階段就已經考慮了可重用性問題。其中一些包,例如Commons里面用來記錄日志的Logging包,最初是為其他項目設計的,例如Jakarta Struts項目,當人們發現這些包對于其他項目也非常有用,能夠極大地幫助其他項目的開發,他們決定為這些包構造一個"公共"的存放位置,這就是Jakarta Commons項目。
為了真正提高可重用性,每一個包都必須不依賴于其他大型的框架或項目。因此,Commons項目的包基本上都是獨立的,不僅是相對于其他項目的獨立,而且相對于Commons內部的大部分其他包獨立。雖然存在一些例外的情況,例如Betwixt包要用到XML API,但絕大部分只使用最基本的API,其主要目的就是要能夠通過簡單的接口方便地調用。
不過由于崇尚簡潔,許多包的文檔變得過于簡陋,缺乏維護和支持,甚至有一部分還有錯誤的鏈接,文檔也少得可憐。大部分的包需要我們自己去找出其用法,甚至有時還需要我們自己去分析其適用場合。本文將逐一介紹這些包,希望能夠幫助你迅速掌握這一積累了許多人心血的免費代碼庫。
說明:Jakarta Commons和Apache Commons是不同的,后者是Apache Software Foundation的一個頂層項目,前者則是Jakarta項目的一個子項目,同是也是本文要討論的主角。本文后面凡是提到Commons的地方都是指Jakarta的Commons。
為了便于說明,本文把Commons項目十八個成品級的組件(排除了EL、Latka和Jexl)分成5類,如下表所示。

必須指出的是,這種分類只是為了方便文章說明,Commons項目里面實際上并不存在這種分類,同時這些分類的邊界有時也存在一定的重疊。
本文首先介紹Web相關類和其他類里面的組件,下一篇文章將涉及XML相關、包裝這兩類,最后一篇文章專門介紹屬于工具類的包。
二、其他類
CLI、Discovery、Lang和Collections包歸入其他類,這是因為它們都各自針對某個明確、實用的小目標,可謂專而精。
2.1 CLI
■ 概況:CLI即Command Line Interface,也就是"命令行接口",它為Java程序訪問和解析命令行參數提供了一種統一的接口。
■ 官方資源:主頁,二進制,源代碼
■ 何時適用:當你需要以一種一致的、統一的方式訪問命令行參數之時。
■ 示例應用:CLIDemo.java。CLASSPATH中必須包含commons-cli-1.0.jar。
■ 說明:
有多少次你不得不為一個新的應用程序重新設計新的命令行參數處理方式?如果能夠只用某個單一的接口,統一完成諸如定義輸入參數(是否為強制參數,數值還是字符串,等等)、根據一系列規則分析參數、確定應用要采用的路徑等任務,那該多好!答案就在CLI。
在CLI中,每一個想要在命令中指定的參數都是一個Option對象。首先創建一個Options對象,將各個Option對象加入Options對象,然后利用CLI提供的方法來解析用戶的輸入參數。Option對象可以要求用戶必須輸入某個參數,例如必須在命令行提供文件名字。如果某個參數是必須的,創建Option對象的時候就要顯式地指定。
下面是使用CLI的步驟。
//?…
//?①??創建一個Options:
Options?options?=?new?Options();
options.addOption("t",?false,?"current?time");
//?…
//?②?創建一個解析器,分析輸入:
CommandLineParser?parser?=?new?BasicParser();
CommandLine?cmd;

try?
{
????cmd?=?parser.parse(options,?args);?

}?catch?(ParseException?pe)?
{
????usage(options);
????return;
}
//?…
//?③?最后就可以根據用戶的輸入,采取相應的操作:

if?(cmd.hasOption("n"))?
{
????System.err.println("Nice?to?meet?you:?"?+
????cmd.getOptionValue('n'));
}這就是使用CLI的完整過程了。當然,CLI還提供了其他高級選項,例如控制格式和解析過程等,但基本的使用思路仍是一致的。請參見本文最后提供的示例程序。
2.2 Discovery
■ 概況:Discovery組件是發現模式(Discovery Pattern)的一個實現,它的目標是按照一種統一的方式定位和實例化類以及其他資源。
■ 官方資源:主頁,二進制,源代碼。
■ 何時適用:當你想用最佳的算法在Java程序中查找Java接口的各種實現之時。
■ 應用實例:DiscoveryDemo.java,MyInterface.java,MyImpl1.java,MyImpl2.java,MyInterface。要求CLASSPATH中必須包含commons-discovery.jar和commons-logging.jar。
■ 說明:
Discovery的意思就是"發現",它試圖用最佳的算法查找某個接口的所有已知的實現。在使用服務的場合,當我們想要查找某個服務的所有已知的提供者時,Discovery組件尤其有用。
考慮一下這種情形:我們為某個特別復雜的任務編寫了一個接口,所有該接口的實現都用各不相同的方式來完成這個復雜任務,最終用戶可以根據需要來選擇完成任務的具體方式。那么,在這種情形下,最終用戶應該用什么辦法來找出接口的所有可用實現(即可能的完成任務的方式)呢?
上面描述的情形就是所謂的服務-服務提供者體系。服務的功能由接口描述,服務提供者則提供具體的實現。現在的問題是最終用戶要用某種辦法來尋找系統中已經安裝了哪些服務提供者。在這種情形下,Discovery組件就很有用了,它不僅可以用來查找那些實現了特定接口的類,而且還可以用來查找資源,例如圖片或其他文件等。在執行這些操作時,Discovery遵從Sun的服務提供者體系所定義的規則。
由于這個原因,使用Discovery組件確實帶來許多方便。請讀者參閱本文后面示例程序中的接口MyInterface.java和兩個實現類MyImpl1.java、MyImple2.java,了解下面例子的細節。在使用Discovery的時候要提供MyInterface文件,把它放入META-INF/services目錄,注意該文件的名字對應接口的完整限定名稱(Fully Qualified Name),如果接口屬于某個包,該文件的名字也必須相應地改變。
//?…
//?①?創建一個類裝入器的實例。
ClassLoaders?loaders?=
????ClassLoaders.getAppLoaders(MyInterface.class,?getClass(),?false);
//?…
//?②?用DiscoverClass的實例來查找實現類。
DiscoverClass?discover?=?new?DiscoverClass(loaders);
//?…
//?③?查找實現了指定接口的類:
Class?implClass?=?discover.find(MyInterface.class);
System.err.println("Implementing?Provider:?"?+?implClass.getName());運行上面的代碼,就可以得到在MyInterface文件中注冊的類。再次提醒,如果你的實現是封裝在包里面的,在這里注冊的名字也應該作相應地修改,如果該文件沒有放在正確的位置,或者指定名字的實現類不能找到或實例化,程序將拋出DiscoverException,表示找不到符合條件的實現。下面是MyInterface文件內容的一個例子:MyImpl2 # Implementation 2。
當然,實現類的注冊辦法并非只有這么一種,否則的話Discovery的實用性就要大打折扣了!實際上,按照Discovery內部的類查找機制,按照這種方法注冊的類將是Discovery最后找到的類。另一種常用的注冊方法是通過系統屬性或用戶定義的屬性來傳遞實現類的名字,例如,放棄META-INF/services目錄下的文件,改為執行java -DMyInterface=MyImpl1 DiscoveryDemo命令來運行示例程序,這里的系統屬性是接口的名字,值是該接口的提供者,運行的結果是完全一樣的。
Discovery還可以用來創建服務提供者的(singleton)實例并調用其方法,語法如下:((MyInterface)discover.newInstance(MyInterface.class)).myMethod();。注意在這個例子中,我們并不知道到底哪一個服務提供者實現了myMethod,甚至我們根本不必關心這一點。具體的情形與運行這段代碼的方式以及運行環境中已經注冊了什么服務提供者有關,在不同的環境下運行,實際得到的服務提供者可能不同。
2.3 Lang
■ 概況:Lang是java.lang的一個擴展包,增加了許多操作String的功能,另外還支持C風格的枚舉量。
■ 官方資源:主頁,二進制,源代碼。
■ 何時適用:當java.lang包提供的方法未能滿足需要,想要更多的功能來處理String、數值和System屬性時;還有,當你想要使用C風格的枚舉量時。
■ 示例應用:LangDemo.java,Mortgage.java,OnTV.java。CLASSPATH中必須包含commons-lang.jar。
■ 說明:
這個包提供了許多出于方便目的而提供的方法,它們中的大多數是靜態的,簡化了日常編碼工作。StringUtils類是其中的一個代表,它使得開發者能夠超越標準的java.lang.String包來處理字符串。使用這些方法很簡單,通常只要在調用靜態方法時提供適當的參數就可以了。例如,如果要將某個單詞的首字符改為大寫,只需調用:StringUtils.capitalise("name"),調用的輸出結果是Name。請瀏覽StringUtils API文檔了解其他靜態方法,也許你會找到一些可以直接拿來使用的代碼。本文提供的示例程序示范了其中一些方法的使用。
另一個值得注意的類是RandomStringUtils,它提供了生成隨機字符串的方法,用來創建隨機密碼實在太方便了。
NumberUtils類提供了處理數值數據的方法,許多方法值得一用,例如尋找最大、最小數的方法,將String轉換成數值的方法,等等。NumberRange和CharRange類分別提供了創建和操作數值范圍、字符范圍的方法。
Builder包里的類提供了一些特殊的方法,可用來構造類的toString、hashCode、compareTo和equals方法,其基本思路就是構造出類的高質量的toString、hashCode、compareTo和equals方法,從而免去了用戶自己定義這些方法之勞,只要調用一下Builder包里面的方法就可以了。例如,我們可以用ToStringBuilder來構造出類的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();
????}
}