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
。
清单 2 展示了处理雇员 XML 的应用程序,清单 3 展示了使用它的客户机应用程序:
EmployeeApplication app = new EmployeeApplication(); app.parseXML(xmlFilePath); |
现在假设模式被更改,为旧模式生成的 Java bean 再也不能使用新模式处理 XML。清单 4 展示了修改后的模式,其中添加了一些新元素,并将名称空间改为 com.ibm.sample.employee:2
,表示这是第二个版本。
要确保向前兼容性,您不希望修改客户机应用程序。但是,输入 XML 的数据源已升级到新的模式,因此应用程序必须能够处理传入的具有任何名称空间的雇员 XML 数据。但是 XMLBeans 无法管理冲突的模式,您应该如何处理这个问题?
![]() |
|
要想应用程序能够处理新的 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 编程中,类加载器是一级对象,并且具有自己的名称空间。类加载器实例加载的类由加载器的名称空间和类名惟一标识。例如,假设您有一个名为 MyClassLoader
的类加载器。如果这个类加载器的一个实例 myLoaderA
加载了类 Foo
,并且另一个实例 myLoaderB
加载了相同的类 Foo
,那么 JVM 将类 Foo
视为两个不同的类并执行两次加载(参见图 1)。
Java 类加载使用一个委托模型来加载类。每个类加载器有一个父类加载器,在尝试加载类本身时,先将类搜索和类加载委托给它的父加载器,即父加载器优先(parent-first)委托。当类加载器存在一个层次结构时,根类加载器(即引导类加载器)将首先尝试加载类。如果不行的话,再由系统类加载器尝试加载,等等(更多有关 Java 类加载的信息,请参阅 参考资料)。
要处理两种不同的模式版本,可以创建不同的类加载器实例,并且让每个实例加载与 XML 模式对应的应用程序和 Java bean 版本。
接着,将创建一个代理,使用公共接口作为原始的应用程序或 bean。代理将为每个 XML 模式版本创建一个类实例。类加载器的父类加载器将是当前线程上下文的类加载器。根据正在处理的 XML 模式,这个代理使用合适的类加载器实例加载与每个 XSD 对应的 Java bean 和应用程序本身。多次加载应用程序是因为当 JVM 尝试加载应用程序使用的所有类时,它将在相同的名称空间中查找。如果没有找到的话,它将抛出 ClassNotFound
异常。图 2 展示了代理的工作方式:
代理可以使用接收的 XML 的名称空间确定要加载哪个 XMLBeans,或者 XML 可以使用一个元素表示 XSD 的版本。代理然后可以使用一个简单 SAX 解析器检索信息。
重新看一下雇员数据管理应用程序。清单 5 展示了代理代码:
清单 5. 代理
在这个清单中,名称空间为 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 展示了将使用代理调用应用程序的代码:
// Create an instance of the proxy instead of the employee application EmployeeApplicationProxy app = new EmployeeApplicationProxy(); app.parseXML(xmlFilePath); |
在这个示例中,应用程序保持不变。但是在某些情况下,可能需要修改应用程序,因为需要使用某些新的处理流程来处理遵循新模式(比如地址)的 XML 数据。因此,需要管理两个应用程序代码集。在这种情况下,您可以加载不同的应用程序版本(或一部分)和特定 XSD 版本的 Java bean。这种方法的优点是您可以最小化应用程序的更改,并在频繁修改模式的情况下轻松地管理应用程序。同样,可以轻松地添加或删除模式版本支持。