级别: 初级 Abdul Rasid, 系统软件工程师, IBM Pallavi Rao, 咨询软件工程师, IBM
2008 年 12 月 01 日 Apache XMLBeans 本身并不支持多个 XML 模式版本。对于需要这种支持来实现兼容性管理的应用程序,这种是一个很大的限制。但是这个问题是可以解决的。在本文中,您将了解到动态类加载技术如何帮助解决问题。
Apache XMLBeans 是一种开源的、与 XML 和 Java™ 绑定的工具,可用来从 XML 模式生成 Java 类和接口。使用生成的 beans,就可以解析或生成遵循模式的 XML 文档。因此,这种绑定会紧密地将生成的 Java 类和 XML 模式耦合在一起。在对 XML 模式执行或大或小的修改时,将重新生成 bean 并使用与修改后的 XML 模式对应的新 bean。至少,它正设法实现这一点。不幸的是,应用程序有时需要支持多个模式版本。例如,如果将 XML 用作数据交换标准,应用程序必须提供向前和向后兼容性,以支持较新或较旧的版本标准。
环境设置
考虑一个雇员数据管理应用程序,其中应用程序使用 XML 文件的形式保存雇员信息,并使用 Apache XMLBeans 进行处理。雇员数据一直由 XSD 定义,如清单 1 所示。模式使用 XMLBeans 模式编译器编译。应用程序然后使用生成的 Java 类和接口处理传入的 XML 文档,这个文档遵循 XSD,名称空间为 com.ibm.sample.employee:1。
清单 1. 雇员 XML 模式的第一个版本
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="com.ibm.sample.employee:1" elementFormDefault="qualified"
xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="com.ibm.sample.employee:1">
<element name="employee" type="tns:employeeType"></element>
<complexType name="employeeType">
<sequence>
<element name="firstName" type="string"></element>
<element name="lastName" type="string"></element>
<element name="department" type="string"></element>
<element name="phone" type="string"></element>
<element name="eEmail" type="string"></element>
</sequence>
</complexType>
</schema>
|
清单 2 展示了处理雇员 XML 的应用程序,清单 3 展示了使用它的客户机应用程序:
清单 2. XMLBeans 应用程序
public class EmployeeApplication {
public void parseXML(String xmlFileName){
EmployeeDocument employeeDoc =
EmployeeDocument.Factory.parse(new File(xml));
EmployeeType employee = employeeDoc.getEmployee();
System.out.println(employee.getFirstName());
// Do something more useful with the employee data
}
}
|
清单 3. 客户机
EmployeeApplication app = new EmployeeApplication();
app.parseXML(xmlFilePath);
|
模式变更简介
现在假设模式被更改,为旧模式生成的 Java bean 再也不能使用新模式处理 XML。清单 4 展示了修改后的模式,其中添加了一些新元素,并将名称空间改为 com.ibm.sample.employee:2,表示这是第二个版本。
清单 4. 雇员 XML 模式的第二个版本
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="com.ibm.sample.employee:2" elementFormDefault="qualified"
xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="com.ibm.sample.employee:2">
<element name="employee" type="tns:employeeType"></element>
<complexType name="employeeType">
<sequence>
<element name="firstName" type="string"></element>
<element name="lastName" type="string"></element>
<element name="dept" type="string"></element>
<element name="eMail" type="string"></element>
<element name="phone" type="string"></element>
<element name="address" type="tns:addressType"></element>
</sequence>
</complexType>
<complexType name="addressType">
<sequence>
<element name="streetName" type="string"></element>
<element name="houseNumber" type="string"></element>
<element name="city" type="string"></element>
<element name="state" type="string"></element>
<element name="zipCode" type="string"></element>
</sequence>
</complexType>
</schema>
|
要确保向前兼容性,您不希望修改客户机应用程序。但是,输入 XML 的数据源已升级到新的模式,因此应用程序必须能够处理传入的具有任何名称空间的雇员 XML 数据。但是 XMLBeans 无法管理冲突的模式,您应该如何处理这个问题?
克服处理限制
 |
常用缩写词
- JRE:Java 运行时环境(Java Runtime Environment)
- JVM:Java 虚拟机(Java Virtual Machine)
- SAX:Simple API For XML
- URL:统一资源定位符(Uniform Resource Locator)
- XML:可扩展标记语言
- XSD:XML 模式定义
|
|
要想应用程序能够处理新的 XML 模式,您将需要生成一组与新模式对应的新 Java bean。因为不希望修改应用程序,因此需要确保 Apache XMLBeans 生成的新 Java bean 具有和旧 bean 相同的包名。这样,应用程序不需要修改代码就可以使用这些 bean。将 com.ibm.sample.employee:1 和 com.ibm.sample.employee:2 名称空间映射到一个单个 Java 包,比如 com.ibm.sample.employee(映射不属于本文讨论范围;更多信息请参阅 参考资料)。
将不同 Java bean 集合对应到不同模式后,您只需根据传入的 XML 加载合适的 bean。可以使用动态类加载技术确保加载正确的 bean 集合。以下小节展示如何使用代理模式和 Java 类加载来加载与每个模式对应的多个 XMLBeans 版本。
Java 类加载
在 Java 编程中,类加载器是一级对象,并且具有自己的名称空间。类加载器实例加载的类由加载器的名称空间和类名惟一标识。例如,假设您有一个名为 MyClassLoader 的类加载器。如果这个类加载器的一个实例 myLoaderA 加载了类 Foo,并且另一个实例 myLoaderB 加载了相同的类 Foo,那么 JVM 将类 Foo 视为两个不同的类并执行两次加载(参见图 1)。
图 1. Java 类加载
Java 类加载使用一个委托模型来加载类。每个类加载器有一个父类加载器,在尝试加载类本身时,先将类搜索和类加载委托给它的父加载器,即父加载器优先(parent-first)委托。当类加载器存在一个层次结构时,根类加载器(即引导类加载器)将首先尝试加载类。如果不行的话,再由系统类加载器尝试加载,等等(更多有关 Java 类加载的信息,请参阅 参考资料)。
要处理两种不同的模式版本,可以创建不同的类加载器实例,并且让每个实例加载与 XML 模式对应的应用程序和 Java bean 版本。
代理
接着,将创建一个代理,使用公共接口作为原始的应用程序或 bean。代理将为每个 XML 模式版本创建一个类实例。类加载器的父类加载器将是当前线程上下文的类加载器。根据正在处理的 XML 模式,这个代理使用合适的类加载器实例加载与每个 XSD 对应的 Java bean 和应用程序本身。多次加载应用程序是因为当 JVM 尝试加载应用程序使用的所有类时,它将在相同的名称空间中查找。如果没有找到的话,它将抛出 ClassNotFound 异常。图 2 展示了代理的工作方式:
图 2. 代理动态加载 XMLBeans
代理可以使用接收的 XML 的名称空间确定要加载哪个 XMLBeans,或者 XML 可以使用一个元素表示 XSD 的版本。代理然后可以使用一个简单 SAX 解析器检索信息。
处理多个模式版本的样例应用程序
重新看一下雇员数据管理应用程序。清单 5 展示了代理代码:
清单 5. 代理
/* These are class variables as only one set of instances of the
* class loaders are required.
* Using instance variables will only proliferate the class loaders and
* the classes loaded by them.
*/
private static URLClassLoader ccl1 = null, ccl2 = null;
private URLClassLoader createURLClassLoader(URL url){
ClassLoader parent = Thread.currentThread().getContextClassLoader();
URLClassLoader ucl = new URLClassLoader(new URL[]{applicationJarURL,url},
parent);
return ucl;
}
private URLClassLoader loadXMLBeans(int choice) {
try{
switch (choice){
case 1:
if ( ccl1 == null){
ccl1 = createURLClassLoader(
new URL("file",null,jarPath+"employee1.jar"));
}
return ccl1;
// Load the latest by default
default :
case 2:
if ( ccl2 == null){
ccl2 = createURLClassLoader(
new URL("file",null,jarPath+"employee2.jar"));
}
return ccl2;
}
public void parseXML(String xmlFileName){
try {
// Determine which version of XML schema is being used.
URLClassLoader ccl = loadXMLBeans(choice);
Class c = ccl.loadClass(
"com.ibm.sample.application.employee.EmployeeApplication");
Class s = String.class;
Object obj = c.newInstance();
Method m = c.getDeclaredMethod("parseXML", new Class[]{s});
m.invoke(obj,new Object[]{xmlFileName});
}
|
 |
在 Java EE 应用程序中使用 XMLBeans
如果使用 XMLBeans 的应用程序是一个 Java 应用程序,那么还可以使用类加载机制。由于大多数应用服务器有自己的类加载结构,因此必须确保 URLClassLoader 实例使用当前线程上下文的类加载器作为父加载器,而不是作为系统类加载器。
|
|
在这个清单中,名称空间为 com.ibm.sample.employee:1 的 XSD 的 Java beans 被归档到名为 employee1.jar 的 JAR 文件中;而名称空间为 com.ibm.sample.employee:2 的 XSD 的 Java bean 被归档到名为 employee2.jar 的 JAR 文件中。在运行时,JAR 文件或是应用程序本身的类文件都不会包含在类路径中,因为它们将由 URLClassLoader 的实例从代理中加载。如果这些类在类路径中可用,URLClassLoader 实例的父加载器(即系统类加载器)将加载它们。同样,不能加载这些 JAR 文件的多个版本,因为类都位于系统类加载器名称空间中。
代理决定输入 XML 使用哪个 XML 模式版本;然后创建一个 URLClassLoader 实例,并将相应 JAR 文件的 URL 添加到 URLClassLoader 的类路径中。代理包含了类加载器的相应实例之后,它将使用 Java 反射来调用原始应用程序(或 bean)的相应方法。这里,我们使用反射的原因是因为我们已经使用非系统类加载器加载了应用程序。
现在雇员数据管理应用程序可以处理遵守任何 XML 模式的雇员数据。清单 6 展示了将使用代理调用应用程序的代码:
清单 6. 修改后的客户机应用程序
// Create an instance of the proxy instead of the employee application
EmployeeApplicationProxy app = new EmployeeApplicationProxy();
app.parseXML(xmlFilePath);
|
在这个示例中,应用程序保持不变。但是在某些情况下,可能需要修改应用程序,因为需要使用某些新的处理流程来处理遵循新模式(比如地址)的 XML 数据。因此,需要管理两个应用程序代码集。在这种情况下,您可以加载不同的应用程序版本(或一部分)和特定 XSD 版本的 Java bean。这种方法的优点是您可以最小化应用程序的更改,并在频繁修改模式的情况下轻松地管理应用程序。同样,可以轻松地添加或删除模式版本支持。
结束语
由此可见,即使不能支持多个模式,您也可以使用 XMLBeans 处理 XML,并且仍然能够管理模式变体。希望您在自己的工作中尝试这种技术。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 源代码样例 | x-xmlbeans-SampleApplication.zip | 10KB | HTTP |
|---|
参考资料 学习
获得产品和技术
- IBM 产品评估试用软件:使用可直接从 developerWorks 下载的 IBM 试用软件构建您的下一个项目,包括来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
作者简介  | 
|  | Abdul Rasid 是位于班加罗尔的 IBM India Software Labs 的系统软件工程师。他目前在 WebSphere RFID Information Center 开发团队工作。 |
 | 
|  | Pallavi Nagesha Rao 在 IT 行业具有超过 8 年的工作经验,担任过各种各样的角色,包括 J2EE 应用程序开发、事务管理系统(TXSeries)和信息管理系统。目前,她在印度软件实验室领导 WebSphere RFID Information Center 的开发。她还曾编写过其他 developerWorks 文章和红皮书。 |
对本文的评价
|