我们经常看到由匿名类型编写的 Web 服务 XML 架构。此架构的作者在构建这类架构时也不太可能会意识到匿名类型可能会导致潜在问题。本文将总结使用匿名类型可能会引起的问题,希望读者能够避免它们。
匿名 XML 类型是 xsd:element 中嵌入的类型。由于它嵌入到 xsd:element 中,因此它没有名称。比如,参见清单 1 。
清单 1. 匿名 “person” 类型
<xsd:element name="person">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="first" type="xsd:string"/>
<xsd:element name="last" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
|
名为 “person” 的元素中的 complexType 本身没有名称,因此它是匿名的。可以按照以下步骤将这个匿名类型转换为命名类型:
- 将 complexType 定义移到全局域中。
- 为 complexType 提供一个名称属性。
- 为全局元素提供一个类型属性,该属性引用新命名的 complexType。
按照以下步骤,将清单 1 中的匿名类型转换为清单 2 中的命名类型。
清单 2. 命名的 Person 类型
<xsd:element name="person" type="Person"/>
<xsd:complexType name="Person">
<xsd:sequence>
<xsd:element name="first" type="xsd:string"/>
<xsd:element name="last" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
|
清单 1 和清单 2 中的架构在逻辑上是一致的。它们都能用于验证清单 3 中的 XML 实例。
清单 3. 命名的 Person 类型
<person>
<first>John</first>
<last>Doe</last>
</person>
|
我们将在下面使用清单 4 和 5 中的架构。清单 4 没有定义一个命令类型。所有类型都是匿名的。为了进行比较,清单 5 显示了具有已命名类型的等价架构。我们将上面的步骤应用到清单 4 以创建清单 5。
清单 4. 使用匿名类型定义的 account 元素
<xsd:element name="account">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="number" type="xsd:int"/>
<xsd:element name="person">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="firstName" type="xsd:string"/>
<xsd:element name="lastName" type="xsd:string"/>
<xsd:element name="otherNamesOnAccount" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="person">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="firstName" type="xsd:string"/>
<xsd:element name="lastName" type="xsd:string"/>
</xsd:sequence>
</xsd:complexTypeb>
</xsd:element>
<xsd:element name="relationshipToAccountOwner" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
|
清单 5. 使用已命名类型定义的 account 元素
<xsd:element name="account" type="tns:Account"/>
<xsd:complexType name="Account">
<xsd:sequence>
<xsd:element name="number" type="xsd:int"/>
<xsd:element name="person" type="tns:AccountOwner"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AccountOwner">
<xsd:sequence>
<xsd:element name="firstName" type="xsd:string"/>
<xsd:element name="lastName" type="xsd:string"/>
<xsd:element name="otherNamesOnAccount" minOccurs="0" maxOccurs="unbounded"
type="tns:OtherNameOnAccount"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="OtherNameOnAccount">
<xsd:sequence>
<xsd:element name="person" type="tns:Person"/>
<xsd:element name="relationshipToAccountOwner" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Person">
<xsd:sequence>
<xsd:element name="firstName" type="xsd:string"/>
<xsd:element name="lastName" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
|
我们为何不喜欢清单 4 中的匿名类型?
- 不能重用
- 还是必须对匿名类型进行命名。
- 清单 4 没有清单 5 简洁。
本文其余部分将详细讨论这三点。
这是匿名类型的主要问题。当在一个元素中以匿名方式定义某个类型时,只有那个元素采用此类型。如果您希望别处的另一个元素也采用相同的类型,最好的方法是将该匿名类型剪切并粘贴到新元素中。
例如,在清单 4 复制元素的 firstName/lastName 对,其中一个位于外部 person 元素中,而另一个位于内部 person 元素中。要支持重用,必须将这两个元素放置到它们各自的命名类型中。如果没有这样的命名类型,这些元素只是从架构的一个点复制到另一个点。
注意,现在在清单 5 中我们拥有这样一个命名类型:Person。清单 5 是清单 4 的命名等价物;我们没有执行任何会影响 XML 实例结构的更改。但现在我们拥有了一个 Person 命名类型,我们就可以在 AccountOwner 中重用 Person 类型以进一步改进清单 5 中的架构。
即使您并不关心重用,使用匿名类型仍然存在潜在的危害。考虑一下 XML 架构是如何被使用的。例如,在一个 Web 服务定义中,XML 架构很有可能会映射到一种编程语言。如果这样,那么在该语言中,类型可能不再是匿名的。它们必须以某种方式命名,而且名称必须由周围的架构派生而来。无论您对其采用什么派生规则,都可能出现打破那些规则的场景。
例如,JAXB 规范中用于将 XML 架构映射到 Java 的规则会在清单 4 中定义的架构中发生故障。JAXB 定义了,匿名类型的类必须采用一个源自容器元素的名称。此外,XML 中的嵌入式匿名复杂类型变成 Java 中的内部类。根据 JAXB,清单 4 中的 XML 架构将映射到下面的 Java 类:
- Account
- Account.Person
- Account.Person.OtherNamesOnAccount
- Account.Person.OtherNamesOnAccount.Person
Java 不允许嵌套类与容器类共享名称。否则将会导致一个编译时间错误,如 “该嵌套类型 Person 无法隐藏封闭类型”。
针对这个问题,我们采取的解决办法是命名所有类型(见清单 5)。您可以看到,我们主要采用 JAXB 使用的名称派生规则 ,即从封装元素的名称中派生出 Java 类型的名称。但我们没有对所有类型采用这种方法,否则将会得到两个 “Person” 类型。因此,我们将第一个 “Person” 类型随意命名为 “AccountOwner”。
尽管我们喜欢这个解决办法,但该办法需要更改 XML 架构。对于那些无法更改架构的情况,JAXB 提供了另一种解决方案。JAXB 允许用户编写一个绑定文件,以告知 JAXB 引擎如何将 XML 映射到 Java。JAXB 规范的作者知道,一个指定映射并不总能适合每种情况,因此他们提供了这种机制来偏离标准。清单 6 中的绑定文件告知 JAXB 生成器修改从第一个 person 元素之下的匿名 complexType 所生成的类,以便将其命名为 “AccountOwner”。
清单 6. 修复清单 4 的问题所需的 JAXB 绑定文件
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xsi="htp://www.w3.org/2001/XMLSchema-instance"
mlns:xsd="http://www.w3.org/2001/XMLSchema"
xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb
http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
jaxb:version="2.1">
<jaxb:bindings schemaLocation="Anonymous.xsd" node="/xsd:schema
/xsd:element[@name='account']/xsd:complexType/xsd:sequence
/xsd:element[@name='person']/xsd:complexType">
<jaxb:class name="AccountOwner"/>
</jaxb:bindings>
</jaxb:bindings>
|
内部 jaxb:bindings 元素包含一个 schemaLocation 属性,告知引擎这个绑定文件影响哪个架构;它包含一个节点属性,该节点属性的值是一个要应用这个特殊绑定的元素中的一个 XPath 表达式。接着 jaxb:class 元素会告诉您,从给定 XPath 的元素所生成的 JAXB 类将被命名为 “AccountOwner”。要详细了解 JAXB 映射文件,请参见 “参考资料” 部分。
JAXB 非常稳健,可允许您通过绑定文件来命名需要命名的匿名类型。但它增加了服务构建流程的难度。您不能依赖所有语言映射来获得这种级别的功能。因此,如果您可以自由修改架构,最好避免匿名类型。
最后,我们认为清单 4 没有清单 5 简洁。我们承认,这个最后的原因完全是主观判断。但我们认为,匿名类型没有命名类型那样的可读性;深层嵌套的 XML 结构通常难以理解。讨论匿名类型结构比讨论命名类型结构更加困难。
基于以下几个原因,应该避免使用匿名类型:
- 它们不允许重用;
- 当映射必须命名匿名类型时,它们可能会引起问题;
- 它们不如命名类型简洁。
可以通过将匿名类型转换为命名类型来解决这些问题。对于 JAXB 从 XML 架构映射到 Java 的情况,如果不能更改架构,可以向 JAXB 引擎提供一个 JAXB 绑定文件,以减轻可能遇到的问题。
学习
- JAXB 2.0 规范。
- 参阅 Oracle 所提供的 Java Web 服务教程中的 JAXB 绑定部分
。
- XML Schema 第 1 部分 和 第 2 部分。
获得产品和技术
- 最受欢迎的 WebSphere 试用软件下载:下载关键 WebSphere 产品的免费试用版。
- IBM developerWorks 软件下载资源中心:IBM deveperWorks 最新的软件下载。
- IBM developerWorks 工具包:下载关键 WebSphere 最新的产品工具包。
讨论
- 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。
- 加入 IBM 软件下载与技术交流群组,参与在线交流。