Jakarta Commons是Jakarta的一个子项目,目的是创建和维护独立于其他框架和产品的程序包(packages)。这些程序包是一些服务于小范围的有效组件的集合,通常适用于服务器端编程。

   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归入小程序类是因为它们都是为了一个小而实用的目的编写的。

1.CLI

       概要:CLI(Command Line Interface)为你的Java程序提供读取和解析命令行参数的通用接口。
     在那得到:主页,程序,源代码。
     何时使用:需要统一操作命令行参数时。
     例子程序: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。

2.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函数,我们也不关心。这个函数的实现取决于以何种方式运行以上代码以及注册的服务提供者。

3.Lang

       概要:java.lang包的扩展,增加许多对String的操作。提供类C语言的枚举。

       在那得到:主页,程序,源代码。这里介绍的是Lang1.0,翻译本文时Lang2.0已经发布。

       何时使用:当对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类还有其他有用的函数。

4.Collections

       概要:对Java Collection框架的扩展,加入了新的数据结构,iterators和比较器。

       在那得到:主页,程序,源代码。

       何时使用:强烈建议在需要处理数据结构的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相关的任务。

1.FileUpload

       概要:现成的文件上传组件。

       在那得到:主页。

       何时使用:当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是它的实现)。

2.HttpClient

       概要:扩展java.net包,提供类似浏览器的功能。

       在那得到:主页,程序,源代码。源代码和程序为beta1版。

       何时使用:当要实现Web浏览器时,或你的程序需要有效的控制HTTP/HTTPS连接时。

       例子程序: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库的很粗略的介绍,它还有很多功能,十分健壮。

3.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相关和打包类,工具类在最后一篇文章中。

       希望你能从这篇文章的例子得到收获。最后,我希望这篇文章能够激起你对Commons子项目的兴趣。