从XML到Java代码的数据绑定之四 从无用的字符到有用的代码段
当前位置:以往代写 > JAVA 教程 >从XML到Java代码的数据绑定之四 从无用的字符到有用的代码段
2019-06-14

从XML到Java代码的数据绑定之四 从无用的字符到有用的代码段

从XML到Java代码的数据绑定之四 从无用的字符到有用的代码段

副标题#e#

在本系列的上一部门中,我演示了如何取出 XML 文档并将它转换成 Java 暗示。这种调动的要害是 XML 文档切合的 XML 模式。模式不只确保了强制约束。它还答允利用 SchemaMapper 来生成 Java 类;那么 XML 文档就可以解包成那些类个中一个的实例。换句话说,这个系统不只需要 XML 文档;文档将酿成其实例的 Java 类不只必需已经存在,并且它还必需在系统的类路径中。

打包类

打包 Java 实例时,环境稍有差异。首先,Unmarshaller 类不存储关于在所建设的 Java 实例中利用的 XML 模式的信息。因此,从 XML 文档建设的 Java 类的实例与任何其它 Java 类没有本质区别。最初,这好像是一个问题。Java 类将实例转回 XML 是否必需采纳更多的操纵?谜底是“是”。然而,这却是功德:从 XML 解包的类完全独立于该 XML 文档。这有以下几个利益:

Java 实例可以解包成差异的 XML 文档。

打包代码不依赖于查找 XML 模式或 XML 文档。

对这个 Java 实例利用反射和其它 Java 要领不会发生意外的字段或要领。

前两点大概是显而易见的,而第三点也许不是。Java 应用措施中正越来越遍及地利用反射,以在运行时处理惩罚各类差异范例的工具。凡是会查抄字段和要领以确定如何利用工具。假如要建设附加要领(如 toXML())或附加字段(如 xmlSchema),类会将这些要领返回到查抄它的应用措施。最终功效是其它应用措施代码大概会错误地利用出格用于打包息争包数据的信息。在数据绑定的情况之外,很大概会误用这些要领和字段(或变量)。它们甚至会给出不应披露的网络资源(如 XML 模式)的应用措施信息。

正是由于这些原因,有须要认可将 Java 打包成 XML 比利用本来的 XML 模式举办打包略有难度。可是,因为有了这个难度,生成的 Java 实例才独立于 XML 文档或模式。假如所有这些令您感想狐疑,请按以下方法思量这个问题:可以将已解包的 Java 工具和已打包的 XML 文档返回给其它应用开拓者,且不必给出任何特定的用法指令。实际上,他们甚至不知道数据本来是 XML 照旧 Java;他们所看到的数据名目只是您提供的名目。

这是一条要记着的重要原则 — 断绝数据检索要领。那就更容易遵守使数据尽大概保持专用的根基指南。譬喻,关于如何从 XML 文档中获取数据的信息并不适合分发给其它应用开拓者,出格是他们在差异的公司(如在商家对商家应用措施中)。

深入研究

我已经报告了一些预备观念,此刻该研究代码了。首先,需要添加一个进口点,以便将 Java 工具打包成 XML 文档。如同 Unmarshaller 类,其它措施最容易从静态要领中利用打包代码。假如没有选项需要传入 Java 工具,就象这里接头的环境,这种方法很有效。同时,遵守好的编程老例、仅内部利用这个静态要领结构工具,以及挪用非静态要领也很重要。这个静态要领是打包 Java 工具的“前门”。除了取出工具举办打包,静态要领还应该可以输出 XML 文档。

这里,需要制止一个常见错误,即答允将 XML 文档输出成 String 或 File。这两种输出要领假设将工具打包成某些永久存储,并生存在物理硬盘上。尽量,工具常常通过网络传播递,如通报到另一个应用措施。可能它可以交给 XSLT 处理惩罚器,该处理惩罚器将 XML 调动成其它形式。这两种环境下,String 或 File 都不能完全办理问题。实际上,应该选取一个较安详的路由,并答允提供用于写入的 OutputStream。这个流可以封装 FileOutputStream,以便写入当地文件、网络毗连可能到另一个措施的管道(如刚提到的 XSLT 处理惩罚器)。确定了这个自变量后,才可以思量 Marshaller 类的进口点。

清单 1. 静态进口点 /**
* <p>
* This method is the public entry point for marshalling an object into
* an XML instance document.
* </p>
*
* @param obj <code>Object</code> to convert to XML.
* @param out <code>OutputStream</code> to write XML to.
* @throws <code>IOException</code> when errors in output occur.
*/
public static void marshall(Object obj, OutputStream out) throws IOException {
Marshaller marshaller = new Marshaller();
marshaller.writeXMLRepresentation(obj, out);
}

看上去很简朴,是吗?很好。除了文章中的这个和其它代码片断,还可以 下载完整的 Marshaller 类 ,并 查察 HTML 名目标类 。

一旦内部建设了 Marshaller类的实例,提供应静态要领的变量就通报到同一要领的动态版本。因为我已经让所提供的流写入功效,所以不需要返回值。制作了前门之后,就该研究屋子的其余部门了。


#p#副标题#e#

声明 XML 模式无关性

#p#分页标题#e#

就象我以前提到过的,不能依赖 XML 模式或任何插到 Java 类中的要领来简化从 Java 到 XML 的转换。相反,我们必需从 Java 类中的数据手工建设 XML 文档。可是,这种抉择很是重大的副浸染是您大概回收 任何 切合 JavaBean 式样名目标 Java 类来生成 XML 暗示。我说 JavaBean 式样 是因为不必实现 JavaBean 接口,但对付类,但愿它对接口的所有数据具有读要领。譬喻,假如类有一个名为 name 的字段,则但愿有一个返回该值的要领 getName。任何没有象这样的读要领的数据字段都将导致在打包进程中忽略该数据。

强制了这种简朴的约束(而且这是尺度编程老例)之后,就可以将任何 Java 工具转换成 XML。这还答允往返转换 XML 数据。XML 文档可以转换成 Java 实例,然后再转换回 XML。可是,有一个申饬:原始 XML 模式声明会丢失!请记着,这就是我们的 Java 打包进程不依赖 XML 模式的副浸染。打包获得的 XML 输出完全不知道任何约束 XML 的 XML 模式。再次将 XML 解包成 Java 时,这不会引起问题,因为类路径中只需要包括必需建设其新实例的暗示工具的类。您可以回已往看一下第三部门,我在解包进程中没有利用 XML 模式。因此,将 XML 转换成 Java,再转换回 XML 并不会产生问题,只是会丢失模式约束。虽然,本系列中的所有代码都是开放源码,因此假如您的应用措施中需要这个成果,接待您将此成果添加到应用措施中。

然后,主要领较量简朴。它接管一个工具,应该返回该工具的 XML 元素暗示。假如工具包括对其它 Java 工具的引用,那么可以利用沟通的要领举办递归,并将一个“子”元素添加到顶级元素。这个最终元素被返回到挪用要领,并输出到所提供的流。(我将在下一节中接头此最终要领。)因为 XML 的 JDOM 暗示易于操纵代码,所以仍利用它。清单 2 中显示了这个焦点要领。

清单 2. 将 Java 工具转换成 XML 的焦点要领 /**
* <p>
* This is the granular portion of binding; a Java <code>Object</code> is
* converted into an XML element (in JDOM form), using recursion for any
* children.
* </p>
*
* @param obj <code>Object</code> to get the XML element representation for.
* @return <code>Element</code> - representation of <code>Object</code> in XML.
* @throws <code>IOException</code> when errors occur in binding.
*/
private Element getXMLRepresentation(Object obj) throws IOException {
Class objectClass = obj.getClass();

// Get the name of the element for this object
String objectName = BindingUtils.initialLowercase(objectClass.getName());

// If this is an "Impl" class, remove that from the name
int index = -1;
if ((index = objectName.indexOf("Impl")) != -1) {
objectName = objectName.substring(0, index);
}

Element element = new Element(objectName);

Method[] methods = objectClass.getMethods();
for (int i=0; i<methods.length; i++) {
// Only want accessor methods, but not the getClass() method in Object
Method method = methods[i];
if ((method.getName().startsWith("get")) &&
(!method.getName().equals("getClass"))) {

// Get the value for this method
try {
Object o = method.invoke(obj, new Object[] { });

// For the name, remove the "get" and lower the initial letter
String propertyName =
BindingUtils.initialLowercase(method.getName().substring(3));

// Determine if it's primitive by seeing if it's a java.lang type
if (o.getClass().getName().startsWith("java.lang.")) {
// If it's a primitive, add as an attribute
element.addAttribute(propertyName, o.toString());
} else { // ... otherwise, recurse and add new element as child
element.addContent(getXMLRepresentation(o));
}

} catch (IllegalAccessException e) {
throw new IOException(e.getMessage()); } catch (InvocationTargetException e) {
throw new IOException(e.getMessage()); }
}
}

return element;
}

可以看到,代码很是简朴。首先,获取 Java 类的名称。这个名称将酿成正在结构的 XML 暗示的元素名称。BindingUtils 中一个新的实用措施要领用于将此名称转换成以小写字母开头的名称。(尺度 XML 名称都以小写字母开头。)在 参考资料 节中可以下载更新的 BindingUtils 类,以及代码包的其余部门。别的,假如类是 Impl 类,将撤除名称的 "Impl" 部门。请记着,在本系列的第三部门中,类就是接口(如 WebServiceConfiguration)和实现(如 WebServiceConfigurationImpl)。另外,这是尺度编程老例,而且该要领将在普通景象中起浸染。

#p#分页标题#e#

一旦元素名称停当,则必需获取属性。每个属性都应该是字段名称和字段的值。将任何巨大工具(非 Java 原语)转换成嵌套元素。要获取此数据,可以利用反射来获取工具的所有可用要领。您只需要体贴读要领(以 "get" 开头);其余可以忽略,包罗从 java.lang.Object 担任的 getClass() 要领。然后,(再次)利用反射来挪用要领,获取它的值。最后,可以再利用 BindingUtils 类从其要领派生出字段名称。因此,要领 getVersion() 将导致建设一个叫做 "version" 的属性,它的值通过挪用 getVersion() 要领发生。

您将留意到以上最后要指出的是字段是 Java 原语(个中类在 java.lang包中)照旧嵌套工具。前一种环境下,属性添加到整个元素。后一种环境下,将产生递合并建设一个子元素。一旦以这种方法处理惩罚了每个读要领,生成的 JDOM 就返回到挪用措施。一旦展开了递归,其功效就是顶级 Java 工具的完整的 XML 暗示,它不能用作 XML 文档的根元素。

#p#副标题#e#

完成打仗

只需一点尽力,您就学会了打包代码。所剩下的就是在输入要领和递归要领之间的偏差之上成立桥梁。利用 JDOM 输出类 org.jdom.output.XMLOutputter 可以很容易就超过这个偏差。这个类利用 JDOM Document 和 OutputStream,输出 XML。虽然,我们有一个流可以利用,还可以将从刚接头过的要领中返回的元素作为文档根元素利用,来建设一个简朴的文档。将这个元素和传播递到 XMLOutputter 中并挪用 output() 要领来实现这个能力。清单 3 中显示了这个要领;它由静态 marshall 要领挪用,并利用我们刚接头过的要领。

清单 3. 将各个细节与 output() 要领毗连 /**
* <p>
* This will take a Java object instance, and convert it into an
* XML document, and write that document to the supplied output stream.
* </p>
*
* @param obj <code>Object</code> to convert to XML.
* @param out <code>OutputStream</code> to write XML to.
* @throws <code>IOException</code> when errors in output occur.
*/
private void writeXMLRepresentation(Object obj, OutputStream out)
throws IOException {

// Root Element is the start of recursion
Element root = getXMLRepresentation(obj);
Document doc = new Document(root);

// Use 2 space indentation and line feeds
XMLOutputter outputter = new XMLOutputter(" ", true);
outputter.output(doc, out);
}

就象任何好的代码一样,您应该放下理论,实际利用此代码。本文的其余部门接头了在实际环境下,首先利用 Marshaller 类,然后利用整个 Marshaller 包。所以,让我们将这些类投入实际利用。

实践出真知

就象任何好的代码一样,您应该放下理论,实际利用此代码。本文的其余部门接头了在实际环境下,首先利用 Marshaller 类,然后利用整个 org.enhydra.xml.binding 包。所以,让我们将这些类投入实际利用。

请记着,在本系列的第三部门中,测试 Unmarshaller 类时做的第一件事就是编写一个相当简朴的类 TestMapper。尽量这个类只能对解包举办根基测试,但它却是开拓数据绑定类进程中的要害部门。虽然,在任何应用措施中,编码新成果后的第一件事就是针对该成果编写一个很是根基的测试。在将新成果放到一个大应用措施中的进程中(凡是是件好玩的事),这凡是只是处理惩罚隐蔽错误的好要领。而有一个测试类可以合用于每个应用措施类,有时合用于每个类的要领(是的,您没有看错),可以节减您的调试时间。有几种好的布局可以辅佐自动执行这些范例的测试:JUnit 是一个很棒的免费测试包、JTest 是一个很好的需付费测试包。请您的公司投资购置一个测试包吧,恒久利用后您会发明它物超所值。

在我宣扬了实际应用的重要性之后,我将接头这个测试类。加上它,可以测试 Unmarshaller 和 Marshaller 类。是的,我知道这粉碎了我刚谈到的法则,但为了使本文的篇幅节制在 20 页以内,我只能这么做。清单 4 中显示了这个类,个中的更新可以辅佐测试新的类。

清单 4. 测试 Marshaller import java.io.File;
import org.enhydra.xml.binding.Marshaller;
import org.enhydra.xml.binding.Unmarshaller;

public class TestMapper {

public static void main(String[] args) {
System.out.println("Starting unmarshalling...");
try {
System.out.println("\n\n......... Start of Unmarshaller test ............\n\n");

File file = new File("xml/example.xml");
Object o = Unmarshaller.unmarshall(file.toURL());
System.out.println("Object class: " + o.getClass().getName());

System.out.println("Casting to WebServiceConfiguration...");
WebServiceConfiguration config = (WebServiceConfiguration)o;
System.out.println("Successful cast.");

System.out.println("Name: " + config.getName());
System.out.println("Version: " + config.getVersion());

System.out.println("Port Number: " + config.getPort().getNumber());
System.out.println("Port Protocol: " + config.getPort().getProtocol());

System.out.println("\n\n......... End of Unmarshaller test ............\n");
System.out.println("\n\n......... Start of Marshaller test ............\n\n");

Marshaller.marshall(o, System.out);
System.out.println("\n\n......... End of Unmarshaller test ............\n");
} catch (Exception e) { e.printStackTrace();
}
}
}

#p#副标题#e#

#p#分页标题#e#

新的代码以突出显示的字体显示(尚有自上一篇文章后添加到测试类的一些有用的调试动静)。新代码的第一段读取第三部门中涵盖的 XML 文档,建设该文档的 Java 暗示,并打印出关于已解包数据的信息。然后,将 Java 工具打包回 XML,并将其功效放到系统的 OutputStream,虽然输出到屏幕上。运行 TestMapper 措施时,其输出雷同于清单 5。

清单 5. 最终功效 $ (/projects/dev/mapper:bmclaugh)> java TestMapper xml/configuration.xsd
Starting unmarshalling...
......... Start of Unmarshaller test ............
Object class: WebServiceConfigurationImpl
Casting to WebServiceConfiguration...
Successful cast.
Name: Unsecured Web Listener
Version: 1.1
Port Number: 80
Port Protocol: http
......... End of Unmarshaller test ............
......... Start of Marshaller test ............
<?xml version="1.0" encoding="UTF-8"?>
<webServiceConfiguration name="Unsecured Web Listener" version="1.1">
<portType protocol="http" number="80" protectedPort="false" />
<documentType index="*.html,*.xml" root="/usr/local/enhydra/html" error="error.html" />
</webServiceConfiguration>

……… End of Unmarshaller test …………

乍看,这里显示的 XML 输出与您当地呆板上的 XML 文档(可以从 参考资料 部门的一个链接中下载)不同很大。可是,仔细调查之后,可以发明两个文档之间只有很少差别。假如忽略元素之间的隔断和缩排,所有属性及其值都与输入文档完全沟通。独一的区别就是 XML 模式引用(和关联名称空间)不见了,正如缺省名称空间声明一样。正如我早先接头过的,这是有意的,使 Java 工具可以独立于其余数据绑定代码而存在。至于缺省名称空间,必需在 Unmarshaller 建设 Java 工具时将关于该名称空间的某些信息存储到该工具中,以便生存。在 XML 应用措施中,可以选择封锁名称空间处理惩罚(使带缺省名称空间的元素等价于不带任何名称空间的元素,因为两者都没有前缀),可能可以修改代码以使它适合您的非凡需要。

在这两种环境下,都可以清楚地看到,有一个从 Java 工具建设 XML 文档的成果性进程。甚至可以插入其它 Java 工具 — 包罗不是从 XML 建设的 Java 工具 — 还可以查察它们的 XML 暗示。我们接着在更深的条理上接头 Marshaller 类,并在更实际的示例中利用它。

#p#副标题#e#

接头 Web 处事

还记得我要接头的 Web 处事吗?它又返来了。前一部门接头了启动 Web 处事是何等简朴,以及如何利用 XML 设置文档来存储其数据。将该数据解包成 Java 工具答允 Web 处事将 XML 数据作为 Java 变量放到它的要领中。数据绑定使获取 XML 数据的进程变得简朴且直接,不必处理惩罚 DOM 可能研究 SAX。

此刻,让我们看看另一方面:封锁 Web 处事并存储数据。这很泛泛,处事在一个端口上启动,(譬喻)尚有一个文档根和错误页面,然后它最终有许大都据字段的值改变了。用户打点处事,同时会做一些修改。可是,封锁处事器时不存储数据将使这些变动丢失。要将此数据放回到本来的 XML 文档中很简朴,请利用清单 6 中显示的 Marshaller。

清单 6. 启动和遏制 Web 处事 import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import myApp.WebServiceConfiguration;
public class WebService() {
/** File to write configuration data to */
private File configurationFile;
/** Configuration variables */
private WebServiceConfiguration config;
public WebService(String configurationFile) throws IOException {
this(new File(configurationFile));
}
public WebService(File configurationFile) throws IOException) {
this.configurationFile = configurationFile;
}
/**
* Various mutator methods for configuration data would be included.
* Each would "proxy" through and set the config object's data.
*/
public void start() throws IOException {
// Obtain the configuration
config = Unmarshaller.unmarshal(configurationFile.toURL());
}
public void stop() throws IOException {
// Save the configuration object
Marshaller.marshal(config, new FileOutputStream(configurationFile));
}
}

#p#分页标题#e#

可以看到,我将 Web 处事的初始设置移到 start() 要领。处事的结构器接管从中装入设置数据的文件。然后,结构器将数据生存到同一个文件中。在 stop() 要领中永久生存数据。别的,处事的所有数据都存储在 config(它存储处事利用的根基数据)中,而不必利用多个成员变量(如 portNumber 或 name)。这就是今后要永久生存的工具。除了使编写 start() 和 stop() 要领变得很简朴(每个要领只有一行!),这个要领还答允 Web 处事存储其它原来就是“姑且的”且无需永久生存的数据。

虽然,类也许还包罗了这里没有谈到的其它要领。可是,已经接头的一些要领显示了装入和存储 XML 数据是何等简朴,甚至不必知道 XML。那么,看了这个例子之后,还什么要接头呢?只有一小部门代码更新,然后就完成了。

慢慢成长的 API(续)

假如您认为所看到的内容是反复的,可能这个标题是从本系列第三部门中抄过来的,不消担忧。正如 JDOM 从第二部门到上一篇文章的不绝变革一样,从上一篇文章到本书中,它也不绝变革着。实际上,最近刊行了 JDOM Beta 5 — 它与前一版本的区别很大。要实现这些改造的成果,数据绑定代码也要不绝改变。幸好,从上一篇文章到本文中,代码中的很多变动都不是很重要,而您的老版本仍可以照常运行。但我照旧发起您利用 参考资料 中的链接,获取各类数据绑定类的最新版本。我在本文的最后编辑阶段,已经用 JDOM 的最新版本(Beta 5)对它们举办了测试。所以,假如您手边是老版本的 JDOM,可能此代码的老版本,可能都是老版本,请操作这个时机进级到最新同时也是最棒的版本。

竣事语

您已经通读了这四篇全面深入的数据绑定文章。假如您已经开始利用尖括号,而且不常利用空格,不必担忧;这可以高级玩意!太好了,您已经开始看到这种要领的强大成果了,而且已经思量如安在应用措施中利用数据绑定类了。下次您编写设置文件、阐明 XML 文档语法可能从 Java 转换成永久存储时,请思量数据绑定是否可以使您的功课更简朴或更有效。

最后请留意:跟着本系列即将竣事,您不必思量代码的成长。文章中提到的代码将并入 Enhydra 应用措施处事器布局中。当您阅读本文时,Enhydra 站点上应该有存放此代码的公司 FTP 处事器的链接可供下载利用。它将被不绝维护、加强,而且不绝重复,直到它可以归并到实际的 Enhydra 应用措施处事器中。所以,请与我们一起使这个开放源码项目酿成更实用、更有效、更适合每小我私家利用的好东西。我们今后还接碰头,也许在 Enhydra,也大概是我即将推出的关于 SOAP、JSP 以及很多更有妙语题的文章中。

    关键字:

在线提交作业