使用 Castor 映射文件
级别: 高级
Brett D. McLaughlin, Sr. (brett@newInstance.com), 作家兼编辑, O'Reilly Media, Inc.
2008 年 3 月 03 日
在阅读了本系列的第 1 部分和第 2 部分之后,您应该已经熟悉如何使用 Castor 把 XML 转换成 Java™,然后再把 Java 转换回 XML。在本文中,将学习如何通过 Castor 映射文件增加灵活性。您将不再受到 XML 文档中的元素名或 Java 类中的成员变量名的限制。
与前一篇文章一样,本文也对您系统的设置情况和您的技能做一些假设。首先,需要按照本系列的第 1 部分中的描述下载并安装 Castor 的最新版本,设置类路径和相关的 Java 库(参见 参考资料 中本系列第一篇文章的链接)。然后,按照第 2 部分中的描述,熟悉 Castor 的基本编组和解组设施。
所以,您应该能够使用 Castor 提取出 XML 文档中的数据,并使用自己的 Java 类处理数据。用数据绑定术语来说,这称为解组(unmarshalling)。反向的过程称为编组(marshalling):您应该能够把 Java 类的成员变量中存储的数据转换为 XML 文档。如果您还不熟悉 Castor 的解组器和编组器,那么应该阅读 第 2 部分(参见 参考资料 中的链接)。
初看上去,您似乎已经掌握了有效地使用 Castor 所需了解的所有过程:设置、编组和解组。但是,您到目前为止学到的所有东西只适用于所谓的理想环境。在这样的环境中,每个人编写的 XML 都是完美的,其中的元素名是有意义的,比如 “title” 和 “authorAddress”,而不是 “t” 或 “aa”。Java 类是按照有组织的方式创建的,采用单数作为类名(比如 “Book”),采用单数名词作为成员变量名(比如 “isbn” 和 “price”)。另外,数据类型也是正确的:没有开发人员把 price
的数据类型设置为 int
而不是 float
,或者使用 char
数组存储字符串数据(这是 C 语言的做法)。
但是,大多数程序员所处的环境并不完美(我真想找到一个能够把我送到完美世界的魔法衣厨)。在大多数程序员所处的环境中有许多不理想的情况:XML 文档常常有糟糕的元素名和属性名,还要应付名称空间问题。元素数据存储在属性中,一些数据甚至由管道符或分号分隔。
Java 类是继承的,对它们进行重新组织在时间和工作量方面的成本可能会超过带来的好处。这些类常常无法简洁地映射到 XML 模式(认为 XML 和数据人员会与 Java 程序员相互妥协的想法也是非常不可思议的),而且在某些情况下,即使实现了简洁映射,也肯定不会跨所有类和数据。XML 元素名可能不合适,许多 Java 变量名也可能不合适。甚至可能遇到使用 Hungarian 表示法的名称,按照这种表示法,所有成员变量都以 “m” 开头,比如 mTitle
。这很不好看。
在这些情况下,您目前学到的数据绑定方法就无能为力了。XML 文档中可能会出现 Hungarian 风格的元素名,Java 类中也可能出现没有意义的结构。这种情况是无法接受的。如果不能按照您希望的方式获取和操作 XML 文档的数据,那么 Castor(或任何数据绑定框架)又有什么意义呢?
首先要注意,在 Castor 或任何其他数据绑定框架中,使用映射文件都要花一些时间。必须先学习一些新语法。尽管映射文件使用 XML 格式(大多数框架都是这样的),但是您需要学习一些新元素和属性。还必须做一些测试,确保 XML 和 Java 代码之间的相互转换产生您希望的结果。最后,如果亲自指定映射,而不是让框架处理映射,就可能在数据绑定中遇到更多的错误。例如,如果希望让框架把 XML 中的 fiddler
元素映射到 Java 代码中的 violin
属性,但是错误地声明这个属性是在 player
类中(应该是在 Player
类中),那么就会遇到错误。因此,在亲自指定映射时,必须非常注意拼写、大小写、下划线、单引号和双引号。
在学习使用映射文件之前,应该确定确实需要这么做。如果掌握了映射文件,但是却不使用它,那就是浪费时间。但是,映射确实有一些优点。
前面曾经提到,在把 XML 转换为 Java 代码时,大小写可能会导致错误。在 XML 中,最常用的做法是名称全部小写并加连字符,比如 first-name
。有时候,甚至会看到 first_name
。这样的名称会转换为很难看的 Java 属性名;没人愿意在代码中调用 getFirst-name()
。实际上,在大多数由程序员(而不是 XML 开发人员或数据管理员)编写的文档中,往往使用驼峰式(camel-case)命名法,比如 firstName
。通过使用映射文件,很容易把 XML 风格的名称(比如 first-name
)映射为 Java 风格的名称(比如 firstName
)。最棒的一点是,不需要强迫 XML 人员像 Java 程序员那样思考,这往往比学习新的映射语法困难得多。
是的,这似乎很明显。既然可以调整 XML 到 Java 的命名转换,反过来肯定也可以:在把 Java 类和属性包含的数据转换为 XML 时,可以修改 Java 名称。但是,有一个更重要,也更微妙的好处:不再受到 Java 类名和包名的限制。
这很可能成为一个组织问题。例如,在大多数情况下,XML 中的嵌套元素转换为类结构,最内层的嵌套元素转换成类属性(成员变量)。看一下清单 1 中的 XML:
<book>
<authors total-sales="0">
<last-name>Finder</last-name>
<first-name>Joseph</first-name>
</authors>
<isbn>9780312347482</isbn>
<title>Power Play</title>
</book>
Castor(或任何其他数据绑定框架)可能假设您需要一个 Book
类,这个类引用几个 Author
类实例。author 类应该有成员变量 lastName
和 firstName
(这里会出现前面提到的命名问题,Author
中的成员变量应该是 last-name
,还是 lastName
?对于名字也有这个问题)。但是,如果这不是您希望的结果,应该怎么办?例如,您可能在一个称为 Person
或 Professional
的类中存储所有作家、会议演讲人和教授。在这种情况下就真有麻烦了,而且您不会愿意全面修改 XML 元素的结构和名称来解决这个问题。实际上,在这种情况下,要想原样保持 XML,使用映射是惟一的办法。
映射允许我们在 Java-XML 转换的两端指定命名方式。我们不希望由于 XML 文档的原因修改 Java 代码,同样不愿意修改 XML 结构来适应 Java 类和成员变量。另外,Java 包也会增加复杂性。尽管在 Castor 中包并不是大问题,但是仍然必须在编组的 XML 中存储 Java 类和包的相关信息,这对于业务逻辑(Java 类)和数据(XML)的隔离很不利。映射可以解决所有这些问题。
前两个问题(对 XML 和 Java 代码的限制)实际上与一个更大的问题相关。大多数情况下,您已经有了一组 Java 对象和一个或多个 XML 文档。因此,不具备前两篇文章中的那种自由度:不能让 Castor 根据它自己的规则把 Java 代码解组为 XML,或者为 XML 文档生成 Java 类。
相反,更为常见的情况是,您需要把一种新技术 — 数据绑定 — 添加到现有的结构中。在这种情况下,映射文件就是使用数据绑定的关键。在两个 “端点”(当前的对象模型和当前的 XML 结构)固定的情况下,映射使我们仍然能够把这两者的数据联系起来。简而言之,良好的映射系统使数据绑定能够在真实环境中发挥作用,而不仅仅停留在理论上。
我们先来看一个简单的映射场景。在前一篇文章中,我们开发了 Book
和 Author
类。清单 2 是 Book
类。
import java.util.LinkedList;
import java.util.List;
public class Book ...{
/** *//** The book's ISBN */
private String isbn;
/** *//** The book's title */
private String title;
/** *//** The authors' names */
private List<Author> authors;
public Book() ...{ }
public Book(String isbn, String title, List<Author> authors) ...{
this.isbn = isbn;
this.title = title;
this.authors = authors;
}
public Book(String isbn, String title, Author author) ...{
this.isbn = isbn;
this.title = title;
this.authors = new LinkedList<Author>();
authors.add(author);
}
public void setIsbn(String isbn) ...{
this.isbn = isbn;
}
public String getIsbn() ...{
return isbn;
}
public void setTitle(String title) ...{
this.title = title;
}
public String getTitle() ...{
return title;
}
public void setAuthors(List<Author> authors) ...{
this.authors = authors;
}
public List<Author> getAuthors() ...{
return authors;
}
public void addAuthor(Author author) ...{
authors.add(author);
}
}
清单 3 是 Author
类。
public class Author ...{
private String firstName, lastName;
public Author() ...{ }
public Author(String firstName, String lastName) ...{
this.firstName = firstName;
this.lastName = lastName;
}
public void setFirstName(String firstName) ...{
this.firstName = firstName;
}
public void setLastName(String lastName) ...{
this.lastName = lastName;
}
public String getFirstName() ...{
return firstName;
}
public String getLastName() ...{
return lastName;
}
}
注意:与前一篇文章相比,惟一的修改是在 Author
中删除了总销售额(totalSales
变量)。我发现它的意义不大,所以在这个版本中删除了它。
这一次不使用前一篇文章中的 XML,而是使用一个不太容易映射的 XML 文档。清单 4 给出希望绑定到 清单 2 和 清单 3 中的 Java 类的 XML 文档。
<book>
<author>
<name first="Douglas" last="Preston" />
</author>
<author>
<name first="Lincoln" last="Child" />
</author>
<book-info>
<isbn>9780446618502</isbn>
<title>The Book of the Dead</title>
</book-info>
</book>
在处理映射文件语法或 Castor 的 API 之前,第一个任务是判断需要把 XML(清单 4)中的哪些数据绑定到 Java 类(清单 2 和 清单 3)。请考虑一会儿。
下面简要总结一下这个 XML 文档应该如何映射到 Java 类:
book
元素应该映射到Book
类的一个实例。- 每个
author
元素应该映射到Author
类的一个实例。 - 每个
Author
实例应该添加到Book
实例中的authors
列表中。 - 对于每个
Author
实例,firstName
应该设置为name
元素上的first
属性的值。 - 对于每个
Author
实例,lastName
应该设置为name
元素上的last
属性的值。 Book
实例的 ISBN 应该设置为book-info
元素中嵌套的isbn
元素的值。Book
实例的书名应该设置为book-info
元素中嵌套的title
元素的值。
其中一些映射您已经知道如何实现了。例如,book
到 Book
类实例的映射是标准的,Castor 会默认处理这个任务。但是,也有一些新东西,比如说作者。尽管把一个 author
元素映射到一个 Author
实例没什么问题,但是没有分组元素,比如 authors
,它清楚地显示出所有作者属于 Book
实例中的一个集合。
这里还有一些元素和属性并不直接映射到 Java 对象模型。Author
类包含表示名字和 姓氏的变量,但是在 XML 中只用一个元素(name
)表示作者姓名,这个元素有两个属性。book-info
元素中嵌套的书名和 ISBN 并不映射到任何 Java 对象。
这种情况非常适合使用映射文件。它使我们能够使用这种 XML(包含我们需要的数据,但是结构不符合希望),仍然能够把文档中的数据放到 Java 对象中。而且,映射文件本身并不难编写。
Castor 中的映射是通过使用映射文件(mapping file) 实现的。映射文件仅仅是一个 XML 文档,它提供了如何在 Java 代码和 XML 之间进行转换的相关信息。因为您熟悉 XML,所以您会发现编写映射文件是非常容易的。实际上,对于简单的映射(只需修改元素名和 Java 类或成员变量的名称),只需一点儿时间就能编写好映射文件。
然后,当进行编组和解组时(前两篇文章已经介绍过如何在程序中进行编组和解组),Castor 会使用这个文件。只需对 Castor 多做一个 API 调用;其他代码都是一样的。
Castor 映射文件的开始是一个普通的 XML 文档,然后是根元素 mapping
。还可以引用 Castor 映射 DTD。这样就可以检验文档,确保结构和语法没有任何问题。这会大大简化对映射的调试。
清单 5 给出最基本的映射文件。
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<!-- All your mappings go here -->
</mapping>
这个文件显然没有实质性内容,但它是所有映射文件的起点。
建立基本的映射文件之后,差不多总是先要把一个 Java 类映射到一个 XML 元素。在这个示例中,需要把 Book
类映射到 book
元素中的数据。映射文件首先考虑类,所以需要添加一个 class
元素,并在这个元素的 name
属性中指定完全限定的 Java 类名,比如:
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>