Jakarta Commons是Jakarta的一個子項目,目的是創建和維護獨立於其他框架和產品的程序包(packages)。這些程序包是一些服務於小范圍的有效組件的集合,通常適用於服務器端編程。
<!-- frame contents -->
<!-- /frame contents -->
Commons項目分為兩部分:Sandbox和Commons庫。Sandbox用於測試。本文關注庫組件,包括它們什麼時候使用,在那裡,以及用例子說明如何使用。
簡要介紹
Jakarta Commons項目源於重用,其中的程序包必須確保能夠重用。有一些包來自於其他項目,例如通用日志包是Jakarta Struts的一部分。當開發者發現某個包對於其他項目很有用,可以縮短開發周期,他們決定將這些包做成通用組件。這就是Jakarta Commons項目。
要真正做到可重用,每個程序包必須獨立於其他較大的框架和項目。因此,Commons項目中的每個包在很大程度上是獨立的,不僅相對於其他項目,甚至對於其他包也是如此。違反這一原則的情況是存在的,但決大多數情況是使用成熟的APIs。例如,Betwixt包建立在XML APIs基礎之上。盡管這個項目的本意是建立不依靠其他組件的程序包。
大多數程序包十分簡潔,以至於缺少必要的文檔、維護和幫助。有些包甚至只有錯誤的連接和極少的文檔。大多數情況下,你只能自己摸索如何使用它們,為什麼使用它們。希望這篇文章對你有幫助。
注重:Jakarta Commons與Apache Commons是不同的。後者是Apache Software Foundation(ASF)的頂級項目。而前者是ASF的另一個頂級項目Jakarta的子項目,是本文介紹的對象。而且,Jakarta Commons只使用Java。在本文中Commons指的是Jakarta Commons。
組件
為了組織方便,我將18個(包括EL,Latka和Jexl)Commons組件分為五類。如下:
組件類別
組件
Web相關
FileUpload,HTTPClient和Net
XML相關
Betwixt,Digester,Jelly和JXPath
工具
BeanUtils,Logging,DBCP,Pool和 Validator
打包
Codec 和 Modeler
小程序
CLI,Discovery,Lang和 Collections
要注重的是這個分類只是對本文而言,在Commons項目中是不存在的。在某種程度上分類是重疊的。本文將介紹Web相關和小程序類,下篇文章包括XML相關和打包類,工具類在最後一篇文章中。
小程序類 將CLI,Discovery,Lang和 Collections歸入小程序類是因為它們都是為了一個小而實用的目的編寫的。
一.CLI 簡介:CLI(Command Line Interface)為你的Java程序提供讀取和解析命令行參數的通用接口。
<!-- frame contents -->
<!-- /frame contents -->
在那得到:主頁,程序,源代碼。
何時使用:需要統一操作命令行參數時。
例子程序:CLIDemo.java,需要將commons-cli-1.0.jarcommons加入CLASSPATH中。
描述:通常在完成一個Java程序時不得不重寫應用程序輸入參數的處理部分。假如有一個唯一的接口用來定義﹑解析和讀取輸入參數,以決定程序的運行方式不是很好嗎?CLI就是答案。
對於CLI,命令行中每個要處理的參數都是一個Option。創建一個Options對象,將Option對象添加進去,然後用CLI提供的函數解析用戶的輸入參數。一個Option也許也需要用戶輸入一個值,例如文件名。這時Option必須在指定處創建。
CLI使用步驟如下:
1.創建Options:
Options options = new Options();
Options.addOption("t",false,"current time");
2.創建解析器解析用戶輸入:
CommandLineParser parser = new BasicParser();
CommandLine cmd;
try{
cmd = parser.parse(options, args);
} catch(ParseException pe) {
usage(options);
return;
}
3.根據用戶輸入執行相應操作:
if(cmd.hasOption("n")) {
System.err.println("Nice to meet you: "+ cmd.getOptionvalue('n'));
}
以上基本就是使用CLI的全過程。當然,CLI提供其他高級選項用於控制各種格式和解析器,但基本操作是相同的。完整的例子可以看demo。
二.Discovery 概要:discovery模式的實現,提供定位與實例化類或其他資源的通用方法。
在那得到:主頁,程序,源代碼。該包處於pre-release狀態。
何時使用:需要快速找到你的代碼中Java接口的實現時。
例子程序:DiscoveryDemo.java,MyInterface.java,MyImpl1.java,MyImpl2.java, MyInterface。需要將commons-discovery.jar和commons-logging.jar添到CLASSPATH中。
描述:Discovery的目的是使用最好的算法得到接口的所有實現。當用戶想找到所有的提供某一服務的提供商時,這將非凡有用。
假設你寫了一個針對某一難題的接口。這個接口的所有實現將以唯一的編碼方式解決這一難題。真正的用戶在實際解決這一難題時將會有多種選擇。他怎麼才能知道接口的那種實現在他的系統中是可行的?
這就是Service與Service Provider結構。Service就是你定義的接口。Service Providers提供Service的實現。用戶需要選擇Service Providers。Discovery組件用多種方法提供幫助。注重Discovery不僅用於發現實現類,而且可以尋找資源,例如圖像或其他文件。它遵照Sun的Service Provider Architecture規范。
同樣,Discovery的使用也很簡單。例子程序中,MyImpl1和MyImpl2是MyInterface接口的實現。MyInterface文件必須在META-INF/services目錄下。注重這個文件必須對應接口的全路徑。假如接口在包內,那麼文件名也要相應改動。
1.取得ClassLoader:
ClassLoaders loaders =
ClassLoaders.getAppLoaders(MyInterface.class, getClass(), false);
2.創建DiscoverClass用於查找實現類:
DiscoverClass discover = new DiscoverClass(loaders);
3.查找實現類:
Class implClass = discover.find(MyInterface.class);
System.err.println("Implementing Provider: " + implClass.getName());
運行以上代碼(DiscoveryDemo.java)將得到MyInterface文件中注冊的類,如下所示。再次提醒,假如實現包含在一個包結構內,文件名必須做相應的修改。假如這個文件不在規定目錄下,或實現類不能實例化或定位,將拋出DiscoveryException異常,表明找不到MyInterface的實現。
MyImpl2 # Implementation 2
當然,這不是注冊實現的唯一方法,否則Discovery還有什麼用!事實上,這是Discovery發現類的內部機制的最後一步。其他方法包括在系統屬性或用戶屬性中定義實現類的名字。例如,刪除META-INF/services目錄下的文件,按以下輸入運行demo,結果相同。這裡系統屬性是接口名,而值是接口實現提供者。
java -DMyInterface=MyImpl1 DiscoveryDemo
Discovery也可用於創建(單例)服務提供者的實例並調用它們的函數。如下:
((MyInterface)discover.newInstance(MyInterface.class)).myMethod();
注重此時我們並不知道那個服務提供者實現myMethod函數,我們也不關心。這個函數的實現取決於以何種方式運行以上代碼以及注冊的服務提供者。
三.Lang 概要:java.lang包的擴展,增加許多對String的操作。提供類C語言的枚舉。
在那得到:主頁,程序,源代碼。這裡介紹的是Lang1.0,翻譯本文時Lang2.0已經發布。
<!-- frame contents -->
<!-- /frame contents -->
何時使用:當對java.lang提供的默認實現感到厭煩,想更好的控制String的操作,數值函數以及系統屬性時,還有,想使用C語言風格的枚舉時。
例子程序:LangDemo.java, Mortgage.java, OnTV.java。需要將commons-lang.jar加入CLASSPATH中。
描述:這個包中提供的很多工具函數可以簡化Java程序員的工作。這些函數可以減少實現日常功能的編程量。非凡是StringUtils類,它提供比標准的java.lang.String包更強的操作字符串的功能。它們的使用十分簡單,只要用正確的參數調用一個靜態函數。例如,要將一句話變為以大寫開頭只要:
StringUtils.capitalise("name");
這個函數的輸出就象我們需要的"Name"。浏覽StringUtils API的其他靜態函數,你可能會發現對你有用的。例子程序中使用了一些。
另一個有趣的類是RandomStringUtils。這個類中的函數產生隨機字符串,這在生成隨機密碼時很有用。
類NumberUtils提供數據操作的函數,包括最大最小值函數,以及將字符串轉換為數字的函數。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();
}
}
為什麼使用這個函數那?因為它使用通用的方法處理所有的數據類型,恰當的返回null,同時可以控制對象和聚集的細節層次。這對於所有builder中的函數有效,而且語法同上所示相近。
作為一個Java程序員,假如你懷念C語言風格的枚舉,那麼這個包中的類型安全的Enum數據類型將填補這一空白。Enum類是抽象類,所以要創建自己的枚舉,必須擴展這個類。如下例所示:
1.定義並擴展枚舉類:
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);
}
}
2.現在就可以在你的方法中使用枚舉了:
OnTV.getEnum("Idol");
該行代碼從我們創建的枚舉數據類型中返回Idol項。Enum類還有其他有用的函數。
四.Collections 概要:對Java Collection框架的擴展,加入了新的數據結構,iterators和比較器。
在那得到:主頁,程序,源代碼。
<!-- frame contents -->
<!-- /frame contents -->
何時使用:強烈建議在需要處理數據結構的Java項目盡可能使用Collections API,從中你會獲得比Java默認實現大的多的好處。
例子程序:CollectionsDemo.java。需要將commons-collections.jar加入CLASSPATH中。
描述:Collections API中有很多類,很難在一節中全介紹出來。所以這裡我著重介紹最重要的類,希望你能感愛好去仔細研究其他類。API附帶的文檔對每個類都有具體的描述。
Bag接口擴展了Java Collections,加入了對所有成員的計數,它在要統計進入進出元素數時很有用。因為Bag是一個接口,所以實際使用的是它的實現類,如HashBag或TreeBag。顧名思義,HashBag是基於HashMap的Bag的實現,而Treebag是基於TreeMap的。Bag接口中的兩個重要方法是getCount(Object o)和uniqueSet()。前者返回Bag中某非凡元素的個數,後者返回Bag中的唯一元素(譯者理解為:元素類型,原文:the unique elements)的集合。
Buffer接口答應按照預定順序從聚集中刪除對象,可以是後進先出,先進先出,或自定義的順序。下面的例子演示了如何以自然排序的順序刪除元素的:
1.BinaryHeap類實現Buffer接口,並按自然排序刪除元素。若想以反自然排序刪除,以false為參數即可:
BinaryHeap heap = new BinaryHeap();
2.添加元素:
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));
3.調用remove方法。按自然排序,-10將被刪除,反之5被刪除:
heap.remove();
FastArrayList﹑FastHashMap和FastTreeMap類使用兩種模式操作相應的Collection類。第一種為慢模式,是這些類初始化後的默認模式。在慢模式下,這些類的結構變化是同步的。在快模式下,對這些類的訪問被認為是只讀的,因此更快一些,而且不發生同步。在快模式下要改變類的結構,要先克隆該類,在克隆類上修改,然後覆蓋該類。這些類在多數訪問為只讀的多線程環境中十分有用。
Iterator包提供了許多常規Java Collections包中沒有的聚集和對象的iterator。例子程序中演示了iterator數組的ArrayIterator。這些iterator同普通Java iterators的用法相同。
最後,comparator包中提供了一些有用的比較器,它們用來定義比較和決定同一類的兩個對象的順序。例如,在我們前面提到的Buffer中,可以定義自己比較器,用它替代自然排序決定順序。如下:
1.這次使用NullComparator創建BinaryHeap。NullComparator根據標志nullAreHigh的值決定null與其他對象的大小。假如取值為false,表示null比其他對象小:
BinaryHeap heap2 = new BinaryHeap(new NullComparator(false));
2.添加對象,包括一些null
heap2.add(null);
heap2.add(new Integer("6"));
heap2.add(new Integer("-6"));
heap2.add(null);
3.最後,執行刪除操作。因為null小於其他對象,Bag最後剩下的是null
heap2.remove();
到這裡,小程序類就介紹完了。更多的細節請看API文檔,或者這些包的源代碼。
Web相關類 Web相關類中組件幫助Java程序員完成Web相關的任務。
一.FileUpload 概要:現成的文件上傳組件。
在那得到:主頁。
<!-- frame contents -->
<!-- /frame contents -->
何時使用:當Java服務器環境中需要簡單易用並且高效的文檔上傳組件時。
例子程序:fileuploaddemo.jsp,fileuploaddemo.htm,msg.jsp。需要將commons-fileupload-1.0-dev.jar添加到程序的WEB-INF/lib目錄下。
描述:FileUpload解決了文件上傳時服務端的常見問題,提供了一個控制文件上傳的易用的接口,可用在JSP頁和servlet中。它符合RFC1867協議標准,解析輸入請求,並將上傳到服務器的一系列文件的分塊交給應用程序。上傳的文件保存在內存中或臨時目錄中(這由一個表示文件大小的參數決定,假如上傳的文件大小超過該參數值,文件將被寫入臨時目錄)。你也可以設置其他參數,例如可接收的文件的最大尺寸以及臨時文件目錄。
FileUpload的使用分為幾步,我將用一個在一個頁中同時上傳兩個不同文件的例子說明。
1.創建Html頁。注重為了確保上傳文件的類型是被答應的,enctype參數必須為multipart/form-data,請求參數method必須為POST。還有一點要注重的是該頁面不但有兩個文件控件還有一個普通文本控件。
<form name="myform" action="fileuploaddemo.jsp"
method="post" enctype="multipart/form-data">
Specify your name:<br />
<input type="text" name="name" size="15"/><br />
Specify your Image:<br />
<input type="file" name="myimage"><br/>
Specify your file&:<br />
<input type="file" name="myfile"><br /><br />
<input type="submit" name="Submit" value="Submit your files"/>
</form>
2.創建JSP頁。
a.檢查輸入請求是不是多段數據。
boolean isMultipart = FileUpload.isMultipartContent(request);
b.創建請求處理器,解析請求,結果存於一個list中。
DiskFileUpload upload = new DiskFileUpload();
List items = upload.parseRequest(request);
c.遍歷這個list訪問每個單獨的文件項。用isFormField()函數區分上傳文件和常規類型域。根據需要,可以逐字節的讀取上傳的文件,或者使用輸入流。
Iterator itr = items.iterator();
while(itr.hasNext()) {
FileItem item = (FileItem) itr.next();
// check if the current item is a form field or an uploaded file
if(item.isFormField()) {
// get the name of the field
String fieldName = item.getFieldName();
// if it is name, we can set it in request to thank the user
if(fieldName.equals("name"))
request.setAttribute("msg", "Thank You: " + item.getString());
} else {
// the item must be an uploaded file save it to disk. Note that there
// seems to be a bug in item.getName() as it returns the full path on
// the client's machine for the uploaded file name, instead of the file
// name only. To overcome that, I have used a workaround using
// fullFile.getName().
File fullFile = new File(item.getName());
File savedFile = new File(getServletContext().getRealPath("/"),
fullFile.getName());
item.write(savedFile);
}
}
可以在上傳處理器中用upload.setSizeMax限制上傳文件的最大尺寸,當上傳文件大小超過該尺寸將會拋出異常。上例中,若將該尺寸設為-1,就可以上傳任何大小的文件。
這個例子還可以有一個小變化。想上面提到的,可以使用輸入流上傳文件。過程是將上傳的內容駐留在內存中直到某一阈值,取得內容的類型,把它們存為字符串或字節數組,最後從內存中刪除。FileItem中函數完成了該過程(DefaultFileItem是它的實現)。
二.HttpClient 概要:擴展java.net包,提供類似浏覽器的功能。
在那得到:主頁,程序,源代碼。源代碼和程序為beta1版。
何時使用:當要實現Web浏覽器時,或你的程序需要有效的控制HTTP/HTTPS連接時。
<!-- frame contents -->
<!-- /frame contents -->
例子程序:HttpClientDemo.java。需要將commons-httpclient.jar和common-logging.jar加入CLASSPATH中,JDK為1.4或更高版本。
描述:HttpClient是java.net的擴展程序包,它提供許多函數幫助你創建基於HTTP協議的各種分布式應用或者嵌入應用程序處理HTTP操作。這個庫提供比Commons的其他包更具體的文檔,並附帶很多例子。這裡將講解怎樣開發一個提取Web網頁的程序。HttpClient附帶的文檔中有一個類似的例子,我將擴展它使它支持SSL。注重這個例子必須運行於JDK 1.4或更高版本上,因為它需要JDK1.4中的Java Secure Socket Connection庫。
1.找一個可以通過HTTPS下載的網頁,例如https://www.paypal.com/。確保文件%JAVA_HOME%/jre/lib/security/java.security有類似如下的一行:
security.provider.2=com.sun.net.ssl.internal.ssl.Provider
這樣,至少在你的程序中處理HTTPS連接的方式是沒有區別的。假如遠端的站點需要驗證,那麼你必須做相應的配置。
2.創建HttpClient類的一個實例,所有的函數都將用到它。這個類包含一個連接治理器操作實際的連接。HttpConnectionManager接口答應你創建自己的治理器,否則可以使用內建的SimpleHttpConnectionManager或MultiThreadedHttpConnectionManager。假如無參數創建HttpClient,那麼默認連接治理器為SimpleHttpConnectionManager。
HttpClient client = new HttpClient();
3.創建一個method實例,用來定義使用那種HTTP方法與遠端站點傳遞信息,可以選擇的方法有GET, POST, PUT, DELETE, HEAD, OPTIONS和TRACE。這些method類是HttpMethod接口的不同實現。在這個例子中使用GetMethod,創建時將要GET的URL作為參數。
HttpMethod method = new GetMethod(url);
4.連接這個URL,也就是用剛才定義的方法連接URL。這個方法將返回server返回的狀態碼。注重executeMethod是client的函數而不是method的。
statusCode = client.executeMethod(method);
5.讀取服務器的返回。假如連接失敗,將拋出HttpException或IOException異常。IOException異常說明是網絡出了問題,重試也不會成功。返回值可以字節數組﹑輸入流或字符串的格式讀取。這樣,就可以隨意處理輸入了。
byte[] responseBody = method.getResponseBody();
6.最後,釋放連接,使之在需要時可重用。
method.releaseConnection();
這時一個關於HttpClient庫的很粗略的介紹,它還有很多功能,十分健壯。
三.Net 概要:基本Internet協議的底層API。
在那得到:主頁,程序,源代碼。
何時使用:當在Java應用程序中需要Internet協議的底層互連時。
例子程序:NetDemo.java。需要將commons-net-1.0.0.jar加入CLASSPATH中。
描述:Net包是很多健壯的和專業的類的集合。這些類來自於一個叫做NetComponents的商業產品的一部分。
Net包中的類既提供對協議的底層訪問也有高層的抽象。在大多數情況下,抽象是足夠的,它可以使你不必編寫解析各種協議的底層套接字的代碼。使用抽象不會損失任何功能。
SocketClient是所有協議的基類,它是一個抽象類包含所有協議的共同功能。各種協議的使用方法是很相近的:首先使用connect方法建立與遠端服務器的連接,執行服務,最後斷開連接。讓我們通過例子來看:
1.創建一個client。我們將使用一個NNTPClient從新聞服務器上下載新聞組列表。
client = new NNTPClient();
2.連接服務器,我用的是新聞組列表較短的服務器。
client.connect("aurelia.deine.net");
3.提取新聞組列表。如下的命令返回NewsGroupInfo數組。假如服務器上沒有新聞組則數組為空,出錯則返回null。注重當新聞組列表很大時,這個命令會花很長時間。每個NewsGroupInfo對象包含關於新聞組的具體信息,有公用函數可以解析它們(如文章數,最後發表的文章,發表權限等)。
list = client.listNewsgroups();
4.最後,斷開與服務器的連接。
if (client.isConnected())
client.disconnect();
其余的client如FingerClient, POP3Client, TelnetClient等用法相似。
結束語 這篇文章介紹了Web相關和小程序類,下篇文章包括XML相關和打包類,工具類在最後一篇文章中。
本文由天極和Matrix共同策劃。