使用JAXB将XML Schema绑定到Java类
Scott Fordin
Java Architecture for XML Binding (JAXB) 是一项可以根据XML 模式产生Java类的Java技术。该过程中,JAXB也提供了将XML实例文档反编组到Java内容树的方法,并能将Java内容树编组回XML实例文档。从另一方面来讲,JAXB提供了快速而简便的方法将XML模式绑定到Java表示,从而使得Java开发者在Java应用程序中能方便地结合XML数据和处理函数。
这意味着你不需要处理甚至不需要知道XML编程技巧就能在Java应用程序中利用平台核心XML数据的灵活性。而且,可以充分利用XML的优势而不用依赖于复杂的XML处理模型如SAX或DOM。JAXB 隐藏了细节并且取消了SAX和DOM中没用的关系——生成的JAXB类仅描述原始模型中定义的关系。其结果是结合了高度可移植Java代码的高度可移植的XML数据,其中这些代码可用来创建灵活、轻便的应用程序和Web服务。
本章介绍了JAXB体系结构、函数和核心概念。在学习第十章之前必须先阅读本章。第十章给出了示例代码和逐步使用JAXB的过程。
JAXB 体系结构
本节主要讨论JAXB处理模型中的组件和交互。在给出了总的概述之后,本节将详细讨论核心的JAXB特性。本节中的主题主要包括:
体系结构概述
JAXB绑定过程
1. 生成类。将XML模式放入JAXB绑定编译器,以产生基于该模式的JAXB类。
2. 编译类。 必须编译所有生成的类、源文件和应用程序代码。
3. 反编组。JAXB绑定框架反编组根据原始模式中的约束编写的XML文档。注意JAXB也支持反编组来自除了文件/文档之外XML数据,如DOM节点、字符串缓冲、SAX Source等等。
4. 生成内容树。 反编组过程产生从生成的JAXB类实例化而来的数据对象内容树,该内容树代表了源XML文档的结构和内容。
5. 验证(可选)。 反编组过程中,可以在生成内容树之前验证源XML文档。注意,如果在第6步中修改内容树,下面,你也能使用JAXB验证操作在将内容编组到XML文档之前验证变化。
6. 处理内容。客户端应用程序通过绑定编译器产生的接口方法可以修改Java内容树表示的XML数据。
7. 编组。将处理过的内容树编组到一个或多个XML输出文档中。在编组之前要验证内容。
通常分时间分阶段执行这两个步骤。典型地,例如,需要在应用程序开发阶段生成并编译JAXB类,并且建立绑定实现,接着是部署阶段,在该阶段使用生成的JAXB类在“现场”产品环境中处理XML内容。
注意:反编组不是创建内容树的唯一的方法。模式派生的内容类通过直接调用合适的工厂方法也支持按计划构建内容树。一旦创建了,任何时候都可以重新验证内容树的一部分或全部。查看示例应用程序 3 中的使用ObjectFactory
类直接给内容树添加内容的例子。
JAXB绑定框架
javax.xml.bind
包定义Unmarshaller
、Validator
和Marshaller
类,它们是提供各自操作的辅助对象。
JAXBContext
类是Java应用程序到JAXB框架的入口点。JAXBContext
实例为反编组、编组和验证操作使用的JAXB实现将XML元素名绑定到Java内容接口。
javax.xml.bind
包也定义了编组或反编组错误出现时、违反约束时及出现其他类型的错误时使用的丰富的验证事件和异常类的层次结构。
下面将详细介绍JAXB绑定框架中的主要的包javax.bind.xml
。
关于javax.xml.bind的更多信息
主要绑定框架包javax.xml.bind
提供的三个核心功能是编组、反编组和验证。到绑定框架的主要的客户端入口点是JAXBContext
类。
JAXBContext
提供了一个抽象,该抽象可以管理实现反编组、编组和验证操作必要的XML/Java绑定信息。客户端应用程序通过newInstance(contextPath)
方法得到该类的新实例。例如:
contextPath
参数包含一个Java包名,这些包包含模式派生的接口—特别是JAXB绑定编译器产生的接口。该参数值初始化JAXBContext
对象,使得它能够管理模式派生的接口。为此目的,JAXB提供程序实现必须提供一个包含下列特征的实现类:
public static JAXBContext createContext( String contextPath,
ClassLoader classLoader )
throws JAXBException;
注意:在每个包含模式派生类的包中,JAXB提供程序实现必须生成一个jaxb.properties
文件。该属性文件必须包含一个叫做javax.xml.bind.context.factory
的属性,它的值是实现createContext
API的类的名字。
不一定要将提供程序提供的类分配给javax.xml.bind.JAXBContext
,它只是必须提供一个实现 createContext
API的类。允许指定多个Java包,JAXBContext
实例允许同时管理多个模式。
关于反编组的更多信息
javax.xml.bind
包中的Unmarshaller
类使得客户端应用程序能够将XML数据转换成Java内容对象树。模式的unmarshal
方法(在命名空间内)允许将模式中声明的任何全局XML元素反编组成实例文档的根。JAXBContext
对象允许在一组模式内合并全局元素(列在contextPath
中
)。由于模式集中的每个模式属于不同的命名空间,将模式统一到反编组上下文中是独立于命名空间的。这意味着客户端应用程序能够反编组contextPath
中列出的任何模式的实例XML文档。例如:
JAXBContext jc = JAXBContext.newInstance(
"com.acme.foo:com.acme.bar" );
Unmarshaller u = jc.createUnmarshaller();
FooObject fooObj =
(FooObject)u.unmarshal( new File( "foo.xml" ) ); // ok
BarObject barObj =
(BarObject)u.unmarshal( new File( "bar.xml" ) ); // ok
BazObject bazObj =
(BazObject)u.unmarshal( new File( "baz.xml" ) );
// error, "com.acme.baz" not in contextPath
客户端应用程序也能明显地生成Java内容树而不是反编组现有XML数据。这样做,应用程序必须能够存取并了解contextPath
中的每个Java包中模式派生的ObjectFactory
类。对于每个模式派生Java类,将有一个静态工厂方法能产生该类型的对象。例如,假设编译了模式之后,得到一个包含PurchaseOrder
模式派生接口的包com.acme.foo
。要创建这类对象,客户端应用程序将使用下列工厂方法:
ObjectFactory objFactory = new ObjectFactory();
com.acme.foo.PurchaseOrder po =
objFactory.createPurchaseOrder();
注意:由于当contextPath
上有多个包时,会产生多个ObjectFactory
类,所以如果你有多个contextPath
上的包,在引用某个包中的ObjectFactory
类时,必须使用完整的包名。
一旦客户端应用程序有模式派生对象的实例,它就能使用赋值方法来设置它的内容。
注意:JAXB 提供程序实现必须在每个包中生成一个类,这些包包含ObjectFactory
包的必要的对象工厂方法和newInstance( javaContentInterface )
方法。
关于编组的更多信息
javax.xml.bind
包中的Marshaller
类使得客户端应用程序能够将Java内容树转换成XML数据。编组一个使用工厂方法人为创建的内容树和反编组操作得到的内容树之间没有什么区别。客户端能够将Java内容树编组回到java.io.OutputStream
或java.io.Writer
的
XML数据中。编组过程能够在注册的ContentHandler
中生成SAX2 事件流或生成DOM Node
对象。
下面是一个简单的例子,它反编组一个XML文档,然后在将它编组回去:
JAXBContext jc = JAXBContext.newInstance( "com.acme.foo" );
// unmarshal from foo.xml
Unmarshaller u = jc.createUnmarshaller();
FooObject fooObj =
(FooObject)u.unmarshal( new File( "foo.xml" ) );
// marshal to System.out
Marshaller m = jc.createMarshaller();
m.marshal( fooObj, System.out );
默认情况下,在java.io.OutputStream
或 java.io.Writer
中生成XML数据时,Marshaller
使用UTF-8 编码。使用setProperty
API 改变编组操作中输出的编码。客户端应用程序提供W3C XML 1.0 推荐中定义的有效的字符编码名(http://www.w3.org/TR/2000/REC-xml-20001006#charencoding
) 并且你的Java平台支持它。
在调用编组API之前,不要求客户端应用程序验证Java内容树。同样,也不要求根据源模式验证Java内容树以便将它编组回XML数据。不同的JAXB提供程序支持编组不同层的无效Java内容树,然而,所有的JAXB提供程序必须能够将有效的内容树编组成XML数据。当JAXB提供程序由于无效的内容而不能完成编组操作时必须抛出MarshalException
。一些JAXB提供程序将完全支持编组无效内容,而其他一些将在遇到第一个验证错误后就失效。
关于验证的更多信息
javax.xml.bind
包中的Validator
类主要用来在运行时控制对内容树的验证。当反编组过程结合了验证,并且验证成功,没有产生任何验证错误,那么就能确保输出的文档和结果内容树是有效的。相对比,编组过程中并不进行验证。如果仅仅编组有效的内容树,这就能保证相对于源模式来说,生成的XML文档总是有效的。
一些XML解析器如SAX和DOM允许取消模式验证功能,并且在一些情况下,为了提高处理速度并且/或者为了处理包含无效或不完整的内容的文档,你可能希望取消模式验证。通过在能使用JAXB的应用程序中选泽的异常处理程序,JAXB支持这些处理方案。总的说来,如果一个JAXB实现不能确切完成反编组或编组,它将抛出一个异常,终止处理。
注意:Validator
类负责管理On-Demand验证(如下所示)。Unmarshaller
类负责管理反编组操作中的 Unmarshal-Time 验证。虽然没有正式的方法能够在编组操作中启动验证,Marshaller
可以监测错误,将它报告给它上面注册的ValidationEventHandler
。
如果客户端应用程序没有在调用验证、反编组或编组方法之前就在它的Validator
、Unmarshaller
或 Marshaller
上设置事件处理程序,那么默认的事件处理程序将接收遇到的任何错误通知。在遇到第一个错误或致命错误后,默认的事件处理程序将挂起当前操作 (但是在接收到警告后将继续该操作)
如果没有通过Validator
、Unmarshaller
或Marshaller
上的setEventHandler
API设置事件处理程序,将会使用默认的事件处理程序。
需要复杂的事件处理的客户端应用程序能够实现ValidationEventHandler
接口并使用Unmarshaller
和/或 Validator
注册它。
为了方便,提供一个特定的事件处理程序,该处理程序仅仅收集反编组、验证和编组操作中创建的ValidationEvent
对象,并且将它们作为java.util.Collection
返回给客户端应用程序。
根据客户端应用程序的配置,使用不同的方法来处理验证事件。然而,在一些情况下,JAXB提供程序不能正确地监测并报告错误。在这些情况下,JAXB提供程序将ValidationEvent
的验证性设置成 FATAL_ERROR
,表明将终止反编组、验证和编组操作。在收到致命错误的通知后,默认的事件处理程序和ValidationEventCollector
工具类必须终止处理。接收到致命错误的通知后,支持它们自己的ValidationEventHandler
客户端应用程序必须也终止处理。如果不终止处理,将会出现意想不到的情况。
由于XML模式是JAXB处理模型的一个重要的组件——并且由于其他数据绑定性能如JAXP使用DTD而不用模式——因此在这里了解一下XML模式的基本概念和它们的工作原理很有用。
XML Schema是描述XML文档中允许的元素、属性、实体和关系的强大的方法。DTD的一个更加强壮的选择,XML模式的目标是定义XML文档的类,该类必须遵守一组特定的结构和数据约束——也就是说,你可能希望为面向章的书、在线采购系统或个人数据库定义不同的模式。在JAXB上下文中,将包含受到XML模式约束的XML文档称作文档实例,并且将文档实例内部的结构和数据叫做内容树。
注意: 实际上,术语“文档”并不总是精确的,因为XML实例文档不一定要是形式完整的、自立的文档文件;相反它具有流的形式,这些流可以是应用程序之间传递的数据、数据库字段集合、XML信息集合,其中信息块包含了描述它们在模式结构中的位置的足够信息。
下面的例子代码来自于W3C Schema 部分 0:入门(http://www.w3.org/TR/2001/REC-xmlschema-0-20010502/),显示了一个XML文档po.xml,是一个简单的购买订单。
<?xml version="1.0"?>
<purchaseOrder orderDate="1999-10-20">
<shipTo country="US">
<name>Alice Smith</name>
<street>123 Maple Street</street>
<city>Mill Valley</city>
<state>CA</state>
<zip>90952</zip>
</shipTo>
<billTo country="US">
<name>Robert Smith</name>
<street>8 Oak Avenue</street>
<city>Old Town</city>
<state>PA</state>
<zip>95819</zip>
</billTo>
<comment>Hurry, my lawn is going wild!</comment>
<items>
<item partNum="872-AA">
<productName>Lawnmower</productName>
<quantity>1</quantity>
<USPrice>148.95</USPrice>
<comment>Confirm this is electric</comment>
</item>
<item partNum="926-AA">
<productName>Baby Monitor</productName>
<quantity>1</quantity>
<USPrice>39.98</USPrice>
<shipDate>1999-05-21</shipDate>
</item>
</items>
根元素purchaseOrder包含子元素shipTo, billTo、comment和items。除了comment 之外的所有子元素包含其他子元素。树的叶子是子元素,如name, street、city和state,它们不包含任何子元素。包含其他子元素或能接受属性的元素叫做复合类型。仅包含PCDATA 并且没有子元素的元素叫做简单类型。
下面的采购模式中定义了po.xml 中的复杂类型和一些简单类型。同样,该例子模式来自于W3C Schema 部分 0:入门(http://www.w3.org/TR/2001/REC-xmlschema-0-20010502/).
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="purchaseOrder" type="PurchaseOrderType"/>
<xsd:element name="comment" type="xsd:string"/>
<xsd:complexType name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="USAddress"/>
<xsd:element name="billTo" type="USAddress"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="items" type="Items"/>
</xsd:sequence>
<xsd:attribute name="orderDate" type="xsd:date"/>
</xsd:complexType>
<xsd:complexType name="USAddress">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="street" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
<xsd:element name="zip" type="xsd:decimal"/>
</xsd:sequence>
<xsd:attribute name="country" type="xsd:NMTOKEN"
fixed="US"/>
</xsd:complexType>
<xsd:complexType name="Items">
<xsd:sequence>
<xsd:element name="item" minOccurs="1"
maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="productName"
type="xsd:string"/>
<xsd:element name="quantity">
<xsd:simpleType>
<xsd:restriction base="xsd:positiveInteger">
<xsd:maxExclusive value="100"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="USPrice" type="xsd:decimal"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="shipDate" type="xsd:date"
minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="partNum" type="SKU"
use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<!-- Stock Keeping Unit, a code for identifying products -->
<xsd:simpleType name="SKU">
<xsd:restriction base="xsd:string">
<xsd:pattern value="\d{3}-[A-Z]{2}"/>
</xsd:restriction>
</xsd:simpleType>
在本例中, 模式同DTD一样包含main或根schema 元素和几个子元素element、complexType和simpleType。和DTD不同,该模式也被指定成属性数据类型,如decimal、 date、 fixed和string。模式也受到如pattern value、 minOccurs和positiveInteger的约束。在DTD中,只能指定文本数据(PCDATA 和CDATA)的数据类型, XML模式支持更加复杂的文本和数值数据类型和约束,这些在Java语言中都有直接对应部分。
注意,本模式中的每个元素都有前缀xsd:,它和W3C XML Schema 命名空间相关。为此,命名空间声明,xmlns:xsd="http://www.w3.org/2001/XMLSchema", 声明的是schema 元素的一个属性。
支持命名空间是XML Schema的另一个重要的特征,因为它为区分根据不同模式书写的元素提供了一个方法或有其他不同的用途,但是它们可能和文档中的其他元素有相同的名字。例如,假设你在你的模式中定义两个命名空间,一个是 foo ,另外一个是bar。结合两个XML 文档,一个来自于开账单数据库,另一个来自于运送数据库,每个都建立在不同的模式下。通过在模式中指定命名空间,你可以区分foo:address 和bar:address。
表示XML内容
本节介绍JAXB 如何将XML内容表示成Java对象。本节主题如下:
将XML名字绑定到Java标识符
XML模式语言使用XML名字——与XML1.0(第二版本) (http://www.w3.org/XML/
)中定义的用来标识模式组件的名字产品匹配的字符串。该字符串集合比有效的Java类、方法和约束标识符的集合大。要解决该差异,JAXB使用几个名字-映射算法。
JAXB名字-映射算法根据标准Java API设计指南将XML名字映射到Java标识符,生成保留了到相应模式连接的标识符,并且不太可能产生冲突。
参考第10章,查看如何改变默认XML名字映射。查看JAXB规范附录C中关于JAXB命名算法的完整细节。
XML模式的Java表示
· 名字,直接来自于XML命名空间URI或由XML命名空间URI绑定自定义指定。
o 每个Java内容接口的实例工厂方法和包中的Java元素接口,例如,给定一个叫做Foo
的Java内容接口,取得的工厂方法是:
o 动态实例工厂分配器,创建指定的Java内容接口的一个实例,例如:
o getProperty
和setProperty
API ,它们允许操纵指定提供程序的属性。
绑定XML Schema
本节介绍了JAXB使用的默认的XML-到-Java绑定。通过自定义绑定声明可以通过自定义的绑定声明全局覆盖或逐层覆盖所有这些绑定。本节主题如下:
· 简单类型定义
简单类型定义
使用简单类型定义的模式组件通常和Java 属性绑定。由于有多种这类模式组件,下列Java特征属性(在模式组件中很常见)包括:
其他Java特征属性是使用simple
类型定义在模式组件中指定的。
默认的数据类型绑定
Java语言提供了比XML模式更加丰富的数据类型集。表 9-3 列出了XML数据类型到JAXB中的Java数据类型的映射。
默认的绑定规则摘要
o 一个命名的简单类型定义,它有来自"xsd:NCName
" 的基本类型,并且有枚举面。
· 将下列XML Schema组件绑定到Java Element接口:
· 将重复出现和复杂的类型定义的模型组和混合的{content type}
绑定到:
o 通用内容属性; 具有Java实例表示元素信息项和字符数据项的列表内容属性 。
自定义JAXB绑定
使用自定义的绑定声明可以在全局范围或逐层地覆盖默认的JAXB绑定。如前面所介绍的,JAXB 使用默认的绑定规则,可以通过下面两种方法可以自定义这些绑定规则:
自定义JAXB绑定声明也能不受XML schema中的XML规范约束自定义生成的JAXB类,让它包括特定于Java改进,如类和包名映射。
你不需要为模式中的每个声明提供一个绑定指令来生成Java类。例如,绑定编译器使用通用的名字影射算法将XML名字绑定到Java编程语言能够接受的名字。然而,如果在类中想要使用不同的名字模式,你可以明确自定义绑定声明让绑定编译器生成不同的名字。在绑定声明中可以进行许多其他自定义,包括:
注意: 依赖于默认的JAXB绑定行为而不是为Java表示的每个XML Schema组件作出绑定声明使得它能够方便地跟上源模式中的变化。在多数情况下, 默认的规则非常强壮,所以不需要任何自定义的绑定声明就能产生可用的绑定。
作用域
当在绑定声明中定义自定义值时,就涉及到作用域。自定义值的作用域就是应用它的模式元素集合。如果将一个自定义值应用到一个模式元素,那么该模式元素就在自定义值的作用域内。
表 9-4 列出了自定义的绑定的四个作用域。
作用域继承
不同的作用域形成了一个分类系统。该分类系统定义了自定义值的继承和覆盖语义。一个作用域内的自定义值被另一个作用域的绑定声明继承使用,下面是继承的层次关系:
· 定义作用域内的模式元素继承模式或全局作用域内定义的自定义值。
· 组件作用域内的模式元素继承定义、模式 或全局作用域内定义的自定义值。
相似的,一个作用域内的自定义值能够覆盖继承自另一个作用域的自定义值,如下所示:
哪些是不支持的
查看JAXB规范的第E.2节“非必需的XML Schema概念”,获得未得到支持的或非必需的模式概念的最新信息。
JAXB API 和工具
JAXB API和工具在Java WSDP的jaxb-1.0
子目录中。该目录包括一组简单应用程序、javadoc API文档、JAXB绑定编译器(xjc
)、javax.xml.bind
包中包含的运行绑定框架API的实现。如果想知道如何使用JAXB,请查看第10章。