在这个系列文章的第一篇中,我们把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。