Web 服务提示和技巧:避免匿名类型

匿名 XML 类型有时会导致 Web 服务中出现问题。本文将解释这些问题并描述如何避免这些问题。

Russell Butek, 认证 IT 专家, IBM

Russell Butek 是 IBM 的一名 SOA 和 Web 服务顾问。他是 IBM WebSphere Web 服务引擎的一名开发人员。他也是 JAX-RPC Java Specification Request (JSR) 专家组的成员。他参与了 Apache 的 AXIS SOAP 引擎的实现,并推动 AXIS 1.0 遵守 JAX-RPC。


developerWorks 投稿作者

Shannon Kendrick, 高级 IT 专家, IBM

Shannon Kendrick 是 IBM Interactive 的一位高级 IT 专家。他主要负责为各种客户开发 Java EE 应用程序,经常在他的客户解决方案中使用 Web 服务。



2011 年 11 月 21 日

简介

我们经常看到由匿名类型编写的 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”。

JAXB 绑定文件

尽管我们喜欢这个解决办法,但该办法需要更改 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 绑定文件,以减轻可能遇到的问题。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=776053
ArticleTitle=Web 服务提示和技巧:避免匿名类型
publish-date=11212011