Jakarta Commons:巧用类和组件三(转)

在这个系列文章的第一篇中,我们把Commons项目包含的组件分成了5类,介绍了Web类和其他类。第二篇文章论及XML类和包装类。这是最后一篇,探讨工具类的组件。注意Commons本身并不进行这种分类,这里进行分类纯粹是为说明和组织方便起见。

工具类包含BeanUtils、

Logging、DBCP、Pool和

Validator这几个组件。

一、BeanUtils

■ 概况:提供了动态操作JavaBean的工具。

■ 官方资源:主页,二进制,源代码。

■ 何时适用:当你需要动态访问JavaBean,但对已编译好的accessor和

modifier一无所知之时。被动态访问的JavaBean必须遵从JavaBeans

specification定义的命名设计规范。

■ 示例应用:BeanUtilsDemo.java,AppLayer1Bean.java,

AppLayer2Bean.java,SubBean.java。要求CLASSPATH中必须包含commons-beanutils.jar、

commons-logging.jar以及commons-collections.jar。

■ 说明:

在动态Java应用程序设计环境中,我们不一定能够预先获知JavaBean的各种set、get方法。即使已经知道了这些方法的名字,为Bean的每个属性依次写出setXXX或getXXX方法也是一件很麻烦的事情。考虑一下这种情形:几个几乎完全相同的Bean从应用的一个层传递到另一个层,你会为每一个属性调用bean1.setXXX(bean2.getXXX())吗?虽然你可以这么做,但并非一定得这么做,因为你可以让BeanUtils为你完成这些繁琐的操作!BeanUtils可以帮助开发者动态地创建、修改和复制JavaBean。

BeanUtils能够操作符合下列条件的JavaBean:

⑴ JavaBean必须提供一个没有参数的构造函数。

⑵ JavaBean的属性必须能够通过getXXX和setXXX方法访问和修改。对于Boolean属性,也允许使用isXXX和setXXX。JavaBean的属性可以是只读或只写的,也就是说,允许只提供属性的set或get方法。

⑶ 如果不采用传统的命名方式(即用get和set),改用其它方式命名JavaBean的accessor和modifier,那么必须通过与JavaBean关联的BeanInfo类声明这一点。

下面来看一个简单的例子。

要获取和设置JavaBean的简单属性,分别使用PropertyUtils.

getSimpleProperty(Object bean, String name)以及PropertyUtils.

setSimpleProperty(Object bean, String name, Object value)方法。如下面的例子所示,其中AppLayer1Bean.java和AppLayer2Bean.java定义了两个测试用的JavaBean。

     
       
     
      PropertyUtils.setSimpleProperty(app1Bean,"intProp1",
  new Integer(10));
System.err.println("App1LayerBean, stringProp1: " + 
   PropertyUtils.getSimpleProperty(app1Bean,
    "stringProp1"));

既然我们可以通过直接调用Bean的方法(app1Bean.getStringProp1()或

app1Bean.setIntProp1(10))来获取或设置Bean的属性,为什么还要使用setSimpleProperty、getSimpleProperty方法呢?这是因为,我们不一定能够预先知道JavaBean属性的名字,因此也不一定知道要调用哪些方法才能获取/设置对应的属性。这些属性的名字可能来自其他过程或外部应用程序设置的变量。因此,一旦搞清楚了JavaBean的属性的名字并把它保存到一个变量,你就可以将变量传递给PropertyUtils,再也不必依靠其他开发者才能预先得知正确的方法名字。

那么,如果JavaBean的属性不是简单数据类型,又该怎么办呢?例如,JavaBean的属性可能是一个Collection,也可能是一个Map。在这种情况下,我们要改用PropertyUtils.getIndexedProperty或PropertyUtils.getMappedProperty。对于集合类属性值,我们必须指定一个索引值,规定待提取或设置的值在集合中的位置;对于Map类属性,我们必须指定一个键,表示要提取的是哪一个值。下面是两个例子:

     
       
     
      PropertyUtils.setIndexedProperty(
    app1Bean, "listProp1[1]", "新字符串1"); 
System.err.println("App1LayerBean, listProp1[1]: " +
    PropertyUtils.getIndexedProperty(app1Bean, 
     "listProp1[1]"));

请注意,对于可索引的属性,索引值是通过方括号传递的。例如上面的例子中,我们把JavaBean(app1Bean)的List中索引为1的值设置成了"新字符串1",后面的一行代码又从索引1的位置提取同一个值。还有另一种方式也可以达到同样的目标,即使用PropertyUtils.setIndexedProperty(Object bean, String name, int index, Object value)和PropertyUtils.getIndexedProperty(Object bean, String name, int index)方法,在这两个方法中索引值作为方法的参数传递。对于Map类属性,也有类似的方法,只要改用键(而不是索引)来获取或设置指定的值。

最后,Bean的属性可能也是一个Bean。那么,怎样来获取或设置那些以属性的形式从属于主Bean的属性Bean呢?只要使用PropertyUtils.getNestedProperty(Object bean, String name)和PropertyUtils.setNestedProperty(Object bean, String name, Object value)方法就可以了。下面提供了一个例子。

     
       
     
      // 访问和设置嵌套的属性
PropertyUtils.setNestedProperty(
    app1Bean, "subBean.stringProp",
    "来自SubBean的信息,通过setNestedProperty设置。"); 
System.err.println(
    PropertyUtils.getNestedProperty(app1Bean, 
     "subBean.stringProp"));

通过上面的例子可以看出,从属Bean的属性是通过一个句点符号访问的。

上述几种访问属性的方式可以结合在一起使用,嵌套深度不受限制。具体要用到的两个方法是PropertyUtils.getProperty(Object bean, String name)和PropertyUtils.setProperty(Object bean, String name, Object value)。例如:PropertyUtils.setProperty(app1Bean, "subBean.listProp[0]", "属性的值");。

这个例子是把嵌套Bean对象和可索引属性结合在一起访问。

BeanUtils经常用于动态访问Web应用中的请求参数。实际上,正是BeanUtils触发了Struts项目中把请求参数动态转换成系统JavaBean的灵感:利用代码把用户填写的表单转换成一个Map,其中参数的名字变成Map中的键,参数的值则来自于用户在表单中输入的数据,然后由一个简单的BeanUtils.populate调用把这些值转换成一个系统Bean。

最后,BeanUtils提供了一个一步到位的方法把数据从一个Bean复制到另一个Bean:

     
       
     
      // 把app1Bean的数据复制到app2Bean
BeanUtils.copyProperties(app2Bean, app1Bean);

BeanUtils还有一些这里尚未提及的实用方法。不过不必担心,BeanUtils是Commons中文档较为完善的组件之一,建议读者参阅BeanUtils包的JavaDoc文档了解其余方法的相关信息。

二、Logging

■ 概况:一个封装了许多流行日志工具的代码库,并提供统一的日志访问接口。

■ 官方资源:主页,二进制,源代码。

■ 何时适用:当你的应用需要一种以上的日志工具之时,或者预期以后会有这种需要之时。

■ 示例应用:LoggingDemo.java,commons-logging.properties。要求CLASSPATH中必须包含

commons-logging.jar,有时还需要log4j.jar。

■ 说明:

日志(Logging)使得我们能够调试和跟踪应用程序任意时刻的行为和状态。在任何规模较大的应用中,Logging都是不可或缺的组成部分,因此现在已经有许多第三方Logging工具,它们免去了开发者自己编写Logging API之劳。实际上,即使JDK也带有构造好了的Logging API。既然已经有这么多选择(log4j,JDK,Logkit,等等),通常我们总是可以找到最适合自己应用要求的现成API。

不过也有可能出现例外的情形,例如一个熟悉的Logging API不能和当前的应用程序兼容,或者是由于某种硬性规定,或者是由于应用的体系结构方面的原因。Commons项目Logging组件的办法是将记录日志的功能封装为一组标准的API,但其底层实现却可以任意修改和变换。开发者利用这个API来执行记录日志信息的命令,由API来决定把这些命令传递给适当的底层句柄。因此,对于开发者来说,Logging组件对于任何具体的底层实现都是中立的。

如果你熟悉log4j,使用Commons的Logging API应该不会有什么问题。即使你不熟悉log4j,只要知道使用Logging必须导入两个类、创建一个Log的静态实例,下面显示了这部分操作的代码:

     
       
     
      import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LoggingDemo {
    private static Log log = LogFactory.getLog
    (LoggingDemo.class);
    // ...
}

有必要详细说明一下调用LogFactory.getLog()时发生的事情。调用该函数会启动一个发现过程,即找出必需的底层日志记录功能的实现,具体的发现过程在下面列出。注意,不管底层的日志工具是怎么找到的,它都必须是一个实现了Log接口的类,且必须在CLASSPATH之中。Commons Logging API直接提供对下列底层日志记录工具的支持:Jdk14Logger,Log4JLogger,LogKitLogger,NoOpLogger (直接丢弃所有日志信息),还有一个SimpleLog。

⑴ Commons的Logging首先在CLASSPATH中寻找一个commons-logging.properties文件。这个属性文件至少必须定义org.apache.commons.logging.Log属性,它的值应该是上述任意Log接口实现的完整限定名称。

⑵ 如果上面的步骤失败,Commons的Logging接着检查系统属性org.apache.commons.logging.Log。

⑶ 如果找不到org.apache.commons.logging.Log系统属性,Logging接着在CLASSPATH中寻找log4j的类。如果找到了,Logging就假定应用要使用的是log4j。不过这时log4j本身的属性仍要通过log4j.properties文件正确配置。

⑷ 如果上述查找均不能找到适当的Logging API,但应用程序正运行在JRE 1.4或更高版本上,则默认使用JRE 1.4的日志记录功能。

⑸ 最后,如果上述操作都失败,则应用将使用内建的SimpleLog。SimpleLog把所有日志信息直接输出到System.err。

获得适当的底层日志记录工具之后,接下来就可以开始记录日志信息。作为一种标准的API,Commons

Logging API主要的好处是在底层日志机制的基础上建立了一个抽象层,通过抽象层把调用转换成与具体实现有关的日志记录命令。

本文提供的示例程序会输出一个提示信息,告诉你当前正在使用哪一种底层的日志工具。请试着在不同的环境配置下运行这个程序,例如,在不指定任何属性的情况下运行这个程序,这时默认将使用

Jdk14Logger;然后指定系统属性-Jorg.apache.commons.logging.Log=org.apache.commons.

logging.impl.SimpleLog再运行程序,这时日志记录工具将是SimpleLog;最后,把Log4J的类放入CLASSPATH,只要正确设置了log4j的log4j.properties配置文件,就可以得到Log4JLogger输出的信息。

三、Pool

■ 概况:用来管理对象池的代码库。

■ 官方资源:主页,二进制,源代码。

■ 何时适用:当你需要管理一个对象实例池之时。

■示例应用:PoolDemo.java和MyObjectFactory.java。要求CLASSPATH中必须有

commons-pool.jar和commons-collections.jar。

-■ 说明:

Pool组件定义了一组用于对象池的接口,另外还提供了几个通用的对象池实现,以及一些帮助开发者自己创建对象池的基础类。

对于大多数开发者来说,对象池应该不算什么新概念了。也许许多读者已经在访问数据库的时候使用过数据库连接池,对象池的概念其实也相似。对象池允许开发者在缓冲区中创建一组对象(创建对象的操作可以通过应用的配置文件完成,或者也可以在应用的启动阶段完成),当应用程序需要用到对象时就可以很快获得相响应。如果应用程序不再需要对象,它仍旧把对象返回给缓冲池,下次需要使用对象时再从缓冲池提取。

Pool组件允许我们创建对象(实例)池,但不限制我们一定要使用某个具体的实现。Pool组件本身提供了几种实现,必要时我们还可以创建自己的实现。

Pool组件包含三个基本的类:ObjectPool,这是一个定义和维护对象池的接口;ObjectPoolFactory,负责创建ObjectPool的实例;还有一个PoolableObjectFacotry,它为那些用于ObjectPool之内的实例定义了一组生命周期方法。

如前面指出的,Pool组件包含几种通用的实现,其中一个就是GenericObjectPool,下面通过一个实例来看看它的用法。

① 创建一个PoolableObjectFactory。这个工厂类定义对象如何被创建、拆除和验证。

     
       
     
      import org.apache.commons.pool.PoolableObjectFactory;

public class MyObjectFactory implements 
 PoolableObjectFactory {
    private static int counter;

    // 返回一个新的字符串
    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) {}
}

本例创建了一个序号不断增加的String对象的池,验证操作(validateObject)总是返回true。

② 利用PoolableObjectFactory创建一个GenericObjectPool,maxActive、maxIdle等选项都采用默认值。

     
       
     
      GenericObjectPool pool = new GenericObjectPool
 (new MyObjectFactory());

③ 从对象池"借用"一个对象。

     
       
     
      System.err.println("Borrowed: " + pool.borrowObject());

④ 把对象返回给对象池。

     
       
     
      pool.returnObject("0");

对象池的状态可以通过多种方法获知,例如:

     
       
     
      // 有多少对象已经激活(已被借用)?
System.err.println("当前活动的对象数量: "
 + pool.getNumActive());

本文后面提供的PoolDemo.java提供了完整的源代码。

四、DBCP

■ 概况:数据库连接池。建立在Pool组件的基础上。

■ 官方资源:主页,二进制,源代码。

■ 何时适用:需要访问关系数据库之时。

■ 示例应用:DBCPDemo.java。要求CLASSPATH中必须有commons-dbcp.jar、commons-pool.jar以及commons-collections.jar。另外还要能够访问数据库,配置适合该数据库的JDBC驱动程序。示例应用测试的是一个MySQL数据库连接,驱动程序是MySQL JDBC driver。注意运行这个程序需要二进制文件的nightly版,当前的正式发行版缺少某些必需的类。最后,运行这个示例程序时,应当确保已经为JDBC驱动程序设置了系统属性(-Djdbc.drivers=com.mysql.jdbc.Driver)。

■ 说明:

DBCP建立在Pool组件的基础上,提供了数据库连接缓冲池机制。与常规的连接池相比,DBCP的使用要稍微复杂一点,因为它的思路是以伪JDBC驱动程序的形式提供一个通用的体系。不过,前面我们已经了解了Pool组件的基本知识,现在要理解DBCP的用法应该也很简单了。

     
       
     
      // ...
// ① 创建一个GenericObjectPool类的实例。

GenericObjectPool pool = new GenericObjectPool(null);
// ...
// ② 在前面讨论Pool组件时提到GenericObjectPool
//  要求有一个PoolableObjectFactory来创建需
//  要缓冲的Object的实例,对于DBCP来说,
//  这一功能现在由PoolableConnectionFactory提
//  供,如下面的例子所示:

DriverManagerConnectionFactory cf =
    new DriverManagerConnectionFactory(
    "jdbc:mysql://host/db", "username", "password"); 

PoolableConnectionFactory pcf =
    new PoolableConnectionFactory(
    CF, pool, null, "SELECT * FROM mysql.db", false, true);
// ...
// ③ 现在,我们只要创建并注册PoolingDriver:

new PoolingDriver().registerPool("myPool", pool);

接下来就可以从这个连接池提取连接了。注意创建这个连接池时采用了maxActive、maxIdle等选项的默认值,如有必要,你可以在前面步骤1创建GenericObjectPool类的实例时自定义这些值。DBCPDemo.java提供了一个完整的实例。

五、Validator

■ 概况:一个收集了常见用户输入验证功能的API。

■ 官方资源:主页,二进制,源代码。

■ 何时适用:对JavaBean执行常规验证操作之时。

■ 示例应用:ValidatorDemo.java,MyValidator.java,MyFormBean.java,validation.xml。要求CLASSPATH中必须有commons-validator.jar,commons-beanutils.jar,commons-collections.jar,commons-digester.jar,以及commons-logging.jar。

■ 说明:

如果你曾经用Struts开发过Web应用,那么应该已经用过Validator包了。Validator包极大地简化了用户输入数据的检验。不过,Validator并不局限于Web应用,它还可以方便地用于其它使用了JavaBean的场合。

Validator允许为用户输入域定义验证条件,支持错误信息国际化,允许创建自定义的验证器,此外,Validator包还提供了一些预定义的可以直接使用的验证器。

验证规则和验证方法用XML文件定义(可以用一个或者多个XML文件定义,但通常而言,把它们分开比较好)。验证方法文件定义了要用到的验证器,指定各个实际实现验证器的Java类(不要求这些类实现某些特定的接口,也不要求这些类必须从特定的类派生,只需要遵从方法定义文件中声明的定义就可以了)。

下面我们就来构造一个自定义的验证器,它的功能是检查Bean的一个String属性是否包含特定的字符("*")。

     
       
     
      import org.apache.commons.validator.*;

public class MyValidator {
    public static boolean validateContainsChar(
     Object bean, Field field) {
    	// 首先获得Bean的属性(即一个String值)
    	String val = ValidatorUtil.getValueAsString
	 (bean, field.getProperty());
    	// 根据属性中是否包含"*"字符,返回true或false。
    	return ((val.indexOf('*') == -1)?false:true);
    }
}

ValidatorUtil类提供了许多实用方法,例如ValidatorUtil.getValueAsString用来提取Bean的属性值并返回一个String。现在我们要在XML文件中声明MyValidator验证器。

     
       
     
      <!-- 定义验证器方法 -->
<global>
    <validator name="containsStar"
    	classname="MyValidator"
    	method="validateContainsChar"
  	methodParams="java.lang.Object, 
	org.apache.commons.validator.Field" />
</global>

可以看到,XML文件详细地定义了验证方法的特征,包括该方法的输入参数。下面来看看使用这个验证器的步骤。

① 在上面的XML文件中加入我们要实现的验证规则。

     
       
     
      <!-- 定义验证规则 -->
<formset>
    <!-- 检查Bean的name属性是否能够通过
      containsStar测试 -->
    <form name="myFormBean">
    	<field property="name" depends="containsStar">
    		<arg0 key="myFormBean.name" />
    	</field> 
    </form>
</formset>

可以看到,所有验证规则都在formset元素之内声明。formset元素之内首先声明要验证的表单,表单之内列出了要验证的输入域及其验证条件。在本例中,我们希望验证myFormBean的name属性,检查该属性是否能够通过containsStar的验证(也即name属性的值是否包含"*"字符)。

② 以XML文件为基础,创建一个Validator实例并予以初始化。

     
       
     
      // 装入验证器XML文件
InputStream in = getClass().getResourceAsStream
("validator.xml");
// 创建一个ValidatorResources
ValidatorResources resources = new ValidatorResources();
// 初始化验证器资源
ValidatorResourcesInitializer.initialize(resources, in);
// 创建Validator
Validator validator = new Validator
 (resources, "myFormBean");
validator.addResource(Validator.BEAN_KEY, bean);

③ 验证Bean。验证的结果是一个ValidatorResults,其中包含了各个要求验证的属性按照各自的验证条件执行验证的结果。

     
       
     
      // 执行验证
ValidatorResults results = validator.validate();

④ 处理ValidationResults。

     
       
     
      //验证结果对象ValidationResults可能还包含了验证其他表单属性的结果,
//对于每一个属性,我们都可以单独提取其验证结果。
ValidatorResult result = results.getValidatorResult
 ("name");

// 对于每一个属性,我们可以分别检查各个验证条件的检查结果。
// 例如,name属性通过了containsStar验证吗?
System.err.println(
    "name属性包含"*"字符的测试结果:" +
    result.isValid("containsStar"));

对于每一个ValidationResult的实例,我们可以查询它是否通过了某项特定的检查。例如,在上面的代码中,我们用result.isValid('containsStart')表达式来检查name属性的ValidatorResult实例,看看它是否通过了containsStar验证。

对于Web应用来说,Validator是一个相当有用的组件,它提供了一组预定义的验证器,极大地方便了用户输入合法性的验证。预定义的验证器可以用来(但不限于)检查输入值的范围、数据类型、长度,以及email地址和地理位置检查。此外,我们还可以自己定义验证器并将它加入到Validator框架之中。

结束语:第三篇(也是最后一篇)介绍 Jakarta Commons的文章就到这里结束。虽然这个系列的文章只涉及了各个组件的基础知识,但希望它们已经足以让你开始下一步的深入研究。

请从这里下载本文代码:jakartacommons3code.zip。


原文链接: http://www.dlog.cn/nicholascoder/diary/9129

你可能感兴趣的:(Jakarta Commons:巧用类和组件三(转))