关键字: Jakarta Commons
来 源: 赛迪网
Jakarta Commons:巧用类和组件2
作者:Vikram Goyal;仙人掌工作室译 发文时间:2003.08.07 15:34:00
在上一篇文章中,我们将Jakarta Commons的组件分成了五类,并介绍了其中的Web类和其他类,本文接着介绍XML类和包装类,接下来的最后一篇文章将介绍工具类。注意Commons本身并不进行这种分类,这里进行分类纯粹是为组织方便起见。
一、包装类
这一类包含Codec和Modeler两个组件。
1.1 Codec
■ 概况:提供常用的编码器和解码器。
■ 官方资源:主页,二进制,源代码。
■ 何时适用:当你需要Base64和Hex编码功能的标准实现之时。
■ 示例应用:CodecDemo.java。要求CLASSPATH必须包含commons-codec-1.1.jar。
■ 说明:
Codec里面的类分成两个包,其中一个包实现的是常用的Base64和Hex编码机制,另一个包是语言、语音方面的编码。两个包的用法相似,鉴于语言、语音的编码并不是很常用,所以下面主要介绍第一个包。
Base64编码主要用于Email传输。定义MIME文档传输的RFC规定了Base 64编码,从而使得任何二进制数据都可以转换成可打印的ASCII字符集安全地传输。例如,假设要通过Email传输一个图形文件,Email客户端软件就会利用Base64编码把图形文件的二进制数据转换成ASCII码。在Base64编码中,每三个8位的字节被编码成一个4个字符的组,每个字符包含原来24位中的6位,编码后的字符串大小是原来的1.3倍,文件的末尾追加"="符号。除了MIME文档之外,Base64编码技术还用于BASIC认证机制中HTTP认证头的"用户:密码"字符串。
Base64类的使用相当简单,最主要的两个静态方法是:Base64.encodeBase64(byte[] byteArray),用于对字节数组中指定的内容执行Base64编码;Base64.decodeBase64(byte[] byteArray),用于对字节数组中指定的内容执行Base64解码。另外,Base64还有一个静态方法Base64.isArrayByteBase64(byte[] byteArray),用于检测指定的字节数组是否可通过Base64测试(即是否包含了经过Base64编码的数据,如前所述,Base64编码的结果只包含可打印的ASCII字符)。
byte[] encodedBytes=Base64.encodeBase64(testString.getBytes());
String decodedString=new String(Base64.decodeBase64(encodedBytes));
System.err.println("/'^/'是一个合法的Base64字符吗?"
+ Base64.isArrayByteBase64(invalidBytes));
Hex编码/解码就是执行字节数据和等价的十六进制表示形式之间的转换。Hex编码的编码、解码过程和Base64相似,此处不再赘述。
1.2 Modeler
■ 概况:根据JMX(Java Management Extensions)规范的定义,支持对Model MBean(Managed Bean)的配置和实例化。
■ 官方资源:主页,二进制,源代码。
■ 何时适用:当你想要创建和管理Model MBean,以便利用标准的管理API来管理应用之时。
■ 示例应用:ModelerDemo.java,DemoManagedBean.java和mbeans-descriptors.xml。要求CLASSPATH中包含commons-modeler-1.0.jar、commons-logging.jar、commons-digester.jar、commons-collections.jar、commons-beanutils.jar,以及Sun的JMX参考实现jmxri.jar。
■ 说明:
下面的说明要求读者对JMX有一定的了解。
Managed Bean简称MBean,是一种关联到应用程序中被管理组件的Bean,是一种对资源抽象。Model MBean是一种特殊的MBean,具有高度动态和可配置的特点,但Model MBean的这种能力是有代价的,程序员需要设置大量的元信息来告诉JMX如何创建Model MBean,这些元信息包括组件的属性、操作和其它信息。Modeler的目的就是降低程序员实现Model MBean的工作量,它提供的一组函数为处理元数据信息带来了方便。另外,Modeler还提供了注册工具和一个基本的Model MBean。
Modeler允许以XML文件的形式定义元数据信息,该XML文件应当遵从随同Modeler提供的DTD定义。元数据信息用来在运行时创建注册信息,注册信息是所有Model MBean的中心知识库,实际上相当于一个创建这类Bean的工厂。
下面我们首先为一个Managed Bean(DemoManagedBean)创建这个XML文件。DemoManagedBean有一个name属性,可读写。
<?xml version="1.0" encoding="GB2312" ?>
<!DOCTYPE mbeans-descriptors PUBLIC
"-//Apache Software Foundation
//DTD Model MBeans Configuration File"
"http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
<!-- JMX MBean的描述 -->
<mbeans-descriptors>
<mbean name="ManagedBean" description="Example Managed Bean"
type="ManagedBean">
<attribute name="name" description="Simple Name"
type="java.lang.String" />
<constructor name="ManagedBean"/>
</mbean>
</mbeans-descriptors>
可以看到,这个XML文件提供了许多ManagedBean的信息,包括它的属性、构造函数,另外还有它的操作(不过本例没有显示),这就是所谓的元数据信息。如果你打算扩展随同Modeler提供的标准MBean(称为BaseModelMBean),可以在mbean元素中以属性的形式指定Model MBean的类名称:。在前面的例子中,标准的Model MBean只是简单地把所有调用直接传递给ManagedBean类。
接下来,我们要注册上述信息。注意通过描述文件装入注册信息之后,我们通过一个静态方法提取格式化的注册信息:
// 创建一个Registry
Registry registry = null;
try {
URL url = ModelerDemo.class.getResource
("mbeans-descriptors.xml");
InputStream stream = url.openStream();
Registry.loadRegistry(stream);
stream.close();
registry = Registry.getRegistry();
} catch (Throwable t) {
t.printStackTrace(System.out);
System.exit(1);
}
创建好Registry之后,我们要创建一个Model MBean,并将它注册到默认的管理服务器。这样,任何JMX客户程序都可以通过Model MBean调用Managed Bean的功能了。
// 获得一个Managed Bean实例的句柄
DemoManagedBean mBean = new DemoManagedBean();
// 创建一个Model MBean,并将它注册到MBean服务器
MBeanServer mServer = registry.getServer();
ManagedBean managed = registry.findManagedBean("ManagedBean");
try {
ModelMBean modelMBean = managed.createMBean(mBean);
String domain = mServer.getDefaultDomain();
ObjectName oName = new ObjectName(domain +
":type=ManagedBean");
mServer.registerMBean(modelMBean, oName);
} catch(Exception e) {
System.err.println(e);
System.exit(0);
}
try {
ObjectName name =
new ObjectName(mServer.getDefaultDomain() +
":type=ManagedBean");
ModelMBeanInfo info = (ModelMBeanInfo) mServer.
getMBeanInfo(name);
System.err.println(" className="+info.getClassName());
System.err.println(" description="+info.getDescription());
System.err.println(" mbeanDescriptor="+info.getMBeanDescriptor());
System.err.println("==== 测试 ====");
System.err.println("Name的原始值: " +
mServer.getAttribute(name, "name"));
mServer.setAttribute(name, new Attribute("name", "Vikram"));
System.err.println("Name的新值: " +
mServer.getAttribute(name, "name"));
} catch(Exception e) {
System.err.println(e);
System.exit(0);
}
虽然这个例子比较简单,但它仍旧清楚地说明了使用Modeler带来的方便,不妨将它与不使用Modeler的情况下创建一个类似的Model MBean相比较。通过XML文件来描述ModelMBeanInfo不仅灵活方便,而且也很容易扩展,比手工编写这类信息改进不少。
二、XML类
XML类包含了与Java、XML技术相关的类,包括:Betwixt,Digester,Jelly,和JXPath。
2.1 Betwixt
■ 概况:实现XML和JavaBean的映射。
■ 官方资源:主页,二进制,源代码。
■ 何时适用:当你想要以灵活的方式实现XML和Bean的映射,需要一个数据绑定框架之时。
■示例应用:BetwixtDemo.java,Mortgage.java,mortgage.xml。要求CLASSPATH中必须包含commons-betwixt-1.0-alpha-1.jar、commons-logging.jar、commons-beanutils.jar、commons-collections.jar、以及commons-digester.jar。
■ 说明:
如果你以前曾经用Castor绑定数据,一定会欣赏Betwixt的灵活性。Castor适合在一个预定义模式(Schema)的基础上执行Bean和XML之间的转换;但如果你只想执行数据和XML之间的转换,最好的选择就是Betwixt。Betwixt的特点就是灵活,能够方便地将数据输出成为人类可阅读的XML。
Betwixt的用法相当简单。如果要把Bean转换成XML,首先创建一个BeanWriter的实例,设置其属性,然后输出;如果要把XML转换成Bean,首先创建一个BeanReader的实例,设置其属性,然后用Digester执行转换。
将Bean转换成XML:
// 用Betwixt将Bean转换成XML必须有BeanWriter的实例。
// 由于BeanWriter的构造函数要求有一个写入器对象,
// 所以我们从创建一个StringWriter开始
StringWriter outputWriter = new StringWriter();
// 注意输出结果并不是格式良好的,所以需要在开始位置
// 写入下面的内容:
outputWriter.write("<?xml version='1.0' ?>");
// 创建一个BeanWriter
BeanWriter writer = new BeanWriter(outputWriter);
// 我们可以设置该写入器的各种属性。
// 下面的第一行禁止写入ID,
// 第二行允许格式化输出
writer.setWriteIDs(false);
writer.enablePrettyPrint();
// 创建一个Bean并将其输出
Mortgage mortgage = new Mortgage(6.5f, 25);
// 将输出结果写入输出设备
try {
writer.write("mortgage", mortgage);
System.err.println(outputWriter.toString());
} catch(Exception e) {
System.err.println(e);
}
将XML转换成Bean:
// 用Betwixt来读取XML数据并以此为基础创建
// Bean,必须用到BeanReader类。注意BeanReader类扩展了
// Digester包的Digester类。
BeanReader reader = new BeanReader();
// 注册类
try {
reader.registerBeanClass(Mortgage.class);
// 并解析它…
Mortgage mortgageConverted =
(Mortgage)reader.parse(new File("mortgage.xml"));
// 检查转换得到的mortgage是否包含文件中的值
System.err.println("Rate: " + mortgageConverted.getRate() +
", Years: " + mortgageConverted.getYears());
} catch(Exception ee) {
ee.printStackTrace();
}
注意,通过BeanReader注册类时,如果顶层元素的名称和类的名称不同,必须用另一个方法注册并指定准确的路径,如reader.registerBeanClass("toplevelelementname", Mortgage.class)。
2.2 Digester
■ 概况:提供友好的、事件驱动的高级XML文档处理API。
■ 官方资源:主页,二进制,源代码。
■ 何时适用:当你想要处理XML文档,而且希望能够根据XML文档中特定的模式所触发的一组规则来执行某些操作时。
■ 示例应用:DigesterDemo.java、 Employee.java、 Company.java、 rules.xml以及company.xml。要求CLASSPATH中必须包含commons-digester.jar、 commons-logging.jar、 commons-beanutils.jar以及commons-collections.jar。
■ 说明:
Digester在解析配置文件的时候最为有用。实际上,Digester最初就是为读取Struts配置文件而开发的,后来才移到Commons包。
Digester是一个强大的模式匹配工具,允许开发者在一个比SAX或DOM API更高的层次上处理XML文档,当找到特定的模式(或找不到模式)时能够触发一组规则。使用Digester的基本思路是:首先创建一个Digester的实例,然后用它注册一系列模式和规则,最后将XML文档传递给它。此后,Digester就会分析XML文档,按照注册次序来触发规则。如果XML文档中的某个元素匹配一条以上的规则,所有的规则会按照注册次序被依次触发。
Digester本身带有12条预定义的规则。当XML文档中找到一个特定的模式时,想要调用某个方法吗?很简单,使用预定义的CallMethodRule!另外,你不一定要使用预定的规则,Digester允许用户通过扩展Rule类定义自己的规则。
在指定模式时,元素必须用绝对名称给出。例如,根元素直接用名称指定,下一层元素则通过"/"符号引出。例如,假设company是根元素,company/employee就是匹配其中一个子元素的模式。Digester允许使用通配符,例如*/employee将匹配XML文档内出现的所有employee元素。
找到匹配的模式时,关联到该匹配模式的规则内有四个回调方法会被调用,它们是:begin,end,body,和finish。这些方法被调用的时刻正如其名字所示,例如调用begin和end的时刻分别是遇到元素的开始标记和结束标记之时,body是在遇到了匹配模式之内的文本时被调用,finish则是在全部对匹配模式的处理工作结束后被调用。
最后,模式可以在一个外部的规则XML文档内指定(利用digester-rules.dtd),或者在代码之内指定,下面要使用的是第一种办法,因为这种办法比较常用。
使用Digester之前要创建两个XML文档。第一个就是数据或配置文件,也就是我们准备对其应用规则的文件。下面是一个例子(company.xml)
<?xml version="1.0" encoding="gb2312"?>
<company>
<name>我的公司</name>
<address>中国浙江</address>
<employee>
<name>孙悟空</name>
<employeeNo>10000</employeeNo>
</employee>
<employee>
<name>猪八戒</name>
<employeeNo>10001</employeeNo>
</employee>
</company>
第二个文件是规则文件rules.xml。rules.xml告诉Digester要在company.xml中查找什么、找到了之后执行哪些操作:
<?xml version="1.0" encoding="gb2312"?>
<digester-rules>
<!-- 创建顶层的Company对象 -->
<object-create-rule pattern="company" classname="Company" />
<call-method-rule pattern="company/name" methodname="setName"
paramcount="0" />
<call-method-rule pattern="company/address"
methodname="setAddress" paramcount="0" />
<pattern value="company/employee">
<object-create-rule classname="Employee" />
<call-method-rule pattern="name" methodname="setName"
paramcount="0" />
<call-method-rule pattern="employeeNo" methodname=
"setEmployeeNo" paramcount="0" />
<set-next-rule methodname="addEmployee" />
</pattern>
</digester-rules>
这个文件有哪些含义呢?第一条规则,<object-create-rule pattern="company" classname="Company" />,告诉Digester如果遇到了模式company,则必须遵从object-create-rule,也就是要创建一个类的实例!那么要创建的是哪一个类的实例呢?classname="Company"属性指定了类的名称。因此,解析company.xml的时候,当遇到顶级的company元素,等到object-create-rule规则执行完毕,我们就拥有了一个Digester创建的Company类的实例。
现在要理解call-method-rule规则也应该不那么困难了,这里call-method-rule的功能是在遇到company/name或company/address模式时调用一个方法(方法的名字通过methodname属性指定)。
最后一个模式匹配值得注意,它把规则嵌套到了匹配模式之中。两种设定规则和模式的方式都是Digester接受的,我们可以根据自己的需要任意选择。在这个例子中,模式里面定义的规则在遇到company/employee模式时创建一个Employee类的对象,设置其属性,最后用set-next-rule将这个雇员加入到顶层的Company。
创建好上面两个XML文件之后,只要用两行代码就可以调用Digester了:
Digester digester = DigesterLoader.createDigester(rules.toURL());
Company company = (Company)digester.parse(inputXMLFile);
第一行代码装入规则文件,创建一个Digester。第二行代码利用该Digester来应用规则。请参见本文后面提供的DigesterDemo.java完整源代码。
2.3 Jelly
■ 概况:一种基于Java和XML的脚本语言。
■ 官方资源:主页,二进制,源代码。
■ 何时适用:简单地说,当你想要一种灵活的、可扩展的XML脚本工具之时。
■ 示例应用:JellyDemo.java,jellydemo.xml以及TrivialTag.java。要求CLASSPATH中必须有commons-jelly-1.0-dev.jar、dom4j.jar、commons-logging.jar、commons-beanutils.jar以及commons-collections.jar。
■ 说明:
要说清楚Jelly到底是什么以及它扮演着哪种角色是件很不容易的事情。Jelly试图提供一个通用的XML脚本引擎,这种脚本引擎是可以由开发者通过定制动作和标记扩展的,XML文档之中的元素映射到JavaBean,而XML元素的属性映射到JavaBean的属性。从某种意义上说,Jelly是一种结合了Betwixt和Digester的工具,但Jelly更强大,具有更好的可扩展性。
一个Jelly系统由多个组件构成。第一个组件是Jelly脚本,它是一种由Jelly引擎解析的XML文档,经过解析的XML文档元素被绑定到Jelly标记动态处理。第二个组件是Jelly标记,它是一种实现了Jelly的Tag接口的JavaBean,凡是Jelly标记都可以实现doTag方法,这个doTag方法就是当脚本引擎遇到XML文档中的特定元素时所执行的方法。Jelly正是通过这一机制实现动态的脚本处理能力,从某种意义上看,有点类似于Digester的工作机制。
Jelly带有许多预定义的标记,其中部分标记提供核心Jelly支持,其他标记用来提供解析、循环、条件执行代码等方面的支持。另外,Jelly还为Ant任务提供了广泛的支持。
要在Java应用程序中使用Jelly,首先要创建一个JellyContext的实例,例如:JellyContext context = new JellyContext();。我们可以把JellyContext对象看成是一个编译和运行Jelly脚本的运行环境。有了JellyContext就可以运行Jelly脚本。JellyContext的输出实际上是一个XMLOutput类的实例:context.runScript(new File("jellydemo.xml"), output);。
创建自定义标记时,我们既可以覆盖上面提到的doTag方法(如下面的例子所示),或者提供一个执行方法,如invoke()或run():
public void doTag(XMLOutput output) throws Exception {
// 在这里加入要执行的操作,
// 例如设置属性、访问文件系统等…
this.intProp = 3;
}
下面提供了一个定义Jelly脚本的XML文件示例:
<j:jelly xmlns:j="jelly:core" xmlns:define="jelly:define"
xmlns:tr="trivialTag">
<define:taglib uri="trivialTag">
<define:jellybean name="trivial" className="TrivialTag" />
</define:taglib>
<tr:trivial intProp="1" stringProp="ball">Hello World</tr:trivial>
</j:jelly>
这个例子用到jelly:define和jelly:core标记,以及一个trivialTag标记。当遇到trivial标记实例时,Jelly创建相应的JavaBean的实例,执行doTag方法(或者也可以是一个run或invoke之类可调用的方法)。
Jelly还有许多其他功能,它既可以直接从命令行或Ant脚本运行,也可以嵌入到应用程序的代码之内,请参见Jelly文档了解详情。
2.4 JXPath
■ 概况:Java中的XPath解释器。
■ 官方资源:主页,二进制,源代码。
■ 何时适用:当你想要在JavaBean、DOM或其他对象构成的结构中应用XPath查询之时。
■ 示例应用:JXPathDemo.java,Book.java,Author.java。要求CLASSPATH必须包含commons-jxpath-1.1.jar。
■ 说明:
下面的说明要求读者已具备基本的XPath知识。
XPath是一种查询XML文档的语言,JXPath将同一概念应用到了其他Java对象的查询,诸如JavaBean、Collection、Array和Map等。
JXPathContext是JXPath中的核心类,它利用一个工厂方法来定位和创建一个上下文的实例。由于有了这一机制,必要时开发者可以插入一个新的JXPath的实现。要使用JXPathContext,只要简单地向它传递一个JavaBean、Collection或Map,例如:JXPathContext context = JXPathContext.newContext(book);。
利用JXPathContext可执行许多任务。例如访问属性或嵌套属性,当然还可以设置属性:
System.err.println(context.getValue("title"));
System.err.println(context.getValue("author/authorId"));
context.setValue("author/authorId", "1001");
利用JXPath还可以查找其他类型的对象,不过创建上下文对象的方式都一样,都是用上面介绍的静态方法获得一个新的上下文,传入想要查询的对象。
结束语:有关包装类和XML类的介绍就到这里结束。在下一篇也是最后一篇文章中,我们将了解工具类的包。
请从这里下载本文代码:jakartacommons2code.zip。
(责任编辑:张明燕)