使用结构定义的类型表示服务接口中的 XML 文档
作者:Sean Brydon、Smitha Kangath 及 Sameer Tyagi
问题描述
开发者在设计 Web 服务交互环境(客户端和服务在其中交换 XML 文档)时,需要选择一种用于表示服务接口中的 XML 文档的类型。类型的选择会带来多方面的影响。选定的类型将在 WSDL 服务描述文件和服务实现代码中反映出来。在 J2EE 1.4 应用程序中使用 JAX-RPC 来交换诸如订单或发票之类的 XML 文档。在设计服务接口时,有许多策略可供考虑来选择表示 XML 文档的类型。
- 使用字符串表示 XML 文档
- 使用结构定义的类型表示 XML 文档
- 使用 XML 片段表示 XML 文档(使用 xsd:anyType)
- 使用 XML 片段表示 XML 文档(使用 xsd:any)
- 使用附件打包 XML 文档
有关所有这些策略的描述,请参见设计面向文档的服务上的目录项。
当选择使用您在结构文件中定义的类型来表示 XML 文档时,该解决方案将重点考虑设计问题。在这种情况下,这些可能是应用程序特有的,或者由垂直行业标准机构定义。例如,应用程序可能需要使用一个 purchaseorder.xml 文档,该文档具有一个相应的 purchaseorder.xsd 结构文件。这种情况下最重要的一点是 WSDL 文件将作为其服务的一部分,该类型是在结构文件中定义的,而不是由标准结构定义的。这种类型通常定义为使用标准结构类型的复杂类型。
解决方案
当使用结构定义的类型表示在客户端和服务之间交换的 XML 文档时,我们首先考虑以下问题。
为了描述此策略,我们将使用一个示例,将 PurchaseOrder XML 文档从客户端传递到能够接收订单的服务。被交换的订单文档是一个典型的订单,它包括诸如订单 ID、联系人信息以及所购商品行项目之类的数据。
策略:使用结构定义的类型表示 XML 文档
在这种策略中,服务接口工件(WSDL 文件、Java 接口和实现)可能包含 PurchaseOrder 结构的所有详细信息,因为该工件将作为接口的一部分。使用 document-literal 格式部署此类 Web 服务时会将 XML 文档传递到 SOAP 消息正文中。对于 WSDL 文件,这意味着此结构的所有元素都会被嵌入 WSDL 文件中,或者可以在单独的结构文件中定义文档结构并将其导入到 WSDL 文件中。对于相应的 Java 接口,这意味着所有 PurchaseOrder 结构元素都需要使用相应的 Java 类型来表示 PurchaseOrder。从 WSDL 角度看,对于此操作,WSDL 与被交换的文档的结构之间有一个耦合。
如果选择将结构定义直接嵌入到 WSDL 文件中,则意味着您的接口将包含文档结构的字段和类型的所有详细信息。通过以下代码示例 1 中的 WSDL 代码片段、以及下面的接口和实现类说明了嵌入到接口中的订单详细信息。在维护时很繁琐,因为订单类型实际上是 WSDL 的一部分。
<?xml version="1.0"
encoding="UTF-8" ?>
<definitions xmlns="http://schema.xmlsoap.org/wsdl/"
xmlns:tns="urn:ObjectPurchaseOrderService"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schema.xmlsoap.org/wsdl/soap/"
name="ObjectPurchaseOrderService"
targetNamespace="urn:ObjectPurchaseOrderService">
<types>
<schema xmlns:soap11-enc="http://schema.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wsdl="http://schema.xmlsoap.org/wsdl/"
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:ObjectPurchaseOrderService">
<xsd:complexType name="Address">
<xsd:sequence>
<xsd:element name="street" type="xsd:string" nillable="false"/>
<xsd:element name="city" type="xsd:string" nillable="false"/>
<xsd:element name="state" type="xsd:string" nillable="false"/>
<xsd:element name="postalCode" type="xsd:string"
nillable="false"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LineItem">
<xsd:sequence>
<xsd:element name="itemId" type="xsd:string" nillable="false"/>
<xsd:element name="price" type="xsd:float" nillable="false"/>
<xsd:element name="quantity" type="xsd:int" nillable="false"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PurchaseOrder">
<xsd:sequence>
<xsd:element name="poId" type="xsd:string" nillable="false"/>
<xsd:element name="createDate" type="xsd:date" nillable="false"/>
<xsd:element name="shipTo" type="Address" nillable="false"/>
<xsd:element name="billTo" type="Address" nillable="false"/>
<xsd:element name="items" type="LineItem" nillable="false"
minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</schema>
</types>
<message name="PurchaseOrderServiceSEI_submitPO">
<part name="PurchaseOrder_1" type="tns:PurchaseOrder" />
</message>
...
</definitions>
代码示例 1:将订单结构定义显示为嵌入到 WSDL 中的参数类型定义的 WSDL 文件代码片段
将文档结构定义嵌入到 WSDL 内的另一种方法是,在单独的文件中定义类型并将其导入到 WSDL 文件中。这样维护起来会更简单。在此策略中,将创建 purchaseorder.xsd 结构文件。然后 WSDL 文件会将此类型导入到 WSDL 中。之后,仅将它用于服务操作的输入参数中。代码示例 2 对此策略进行了说明。
<types>
<schema
targetNamespace="urn:SchemaDefinedPurchaseOrderService"
xmlns:soap11-enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://www.w3.org/2001/XMLSchema">
<xsd:import
namespace="http://java.sun.com/blueprints/ns/po"
schemaLocation="PurchaseOrder.xsd"/>
<element name="submitPO">
<complexType>
<sequence>
<element name="inputDoc" type="pons:PurchaseOrder"
nillable="true"/>
</sequence>
</complexType>
</element>
...
</schema>
</types>
<message
name="SchemaDefinedPurchaseOrderServiceSEI_submitPO">
<part name="parameters"
element="tns:submitPO"/>
</message>
...
<portType name="SchemaDefinedPurchaseOrderServiceSEI">
<operation name="submitPO">
<input
message="tns:SchemaDefinedPurchaseOrderServiceSEI_submitPO"/>
...
</operation>
</portType>
代码示例 2:显示所导入的订单结构的 WSDL 中的代码片段
现在,看一下将从这些类型的 WSDL 文件生成的 Java 文件。正如 WSDL 文件必须包含为订单指定的所有详细信息一样,Java 接口也需要反映表示订单的类型。
//The service endpoint java
interface(either generated or hand rolled)
package com.sun.j2ee.blueprints.objectposervice;
import java.rmi.*;
public interface PurchaseOrderServiceSEI
extends
Remote {
public String submitPO(PurchaseOrder inputDoc)
throws
InvalidPOException,
RemoteException;
}
代码示例 3:PurchaseOrderServiceSEI Java 接口,对应于服务的 WSDL 文件
请注意,PurchaseOrder.java 会以类的形式进行定义,与它引用的任何类的类型,例如 Address.java 或 LineItem.java 的定义一样
public class PurchaseOrder{
private String poId;
private Calendar createDate;
private Address shipTo;
private Address billTo;
private LineItem[] items;
...
}
代码示例 4:订单 Java 类,对应于服务的 WSDL 文件中的类型定义
同样,端点实现(此示例中的 EJB)也在其方法签名中使用 PurchaseOrder.java。请注意,该组件可以是 Web 组件,而不能是 EJB 组件,并且会出现同样的问题。就选择 XML 文档的类型而言,选择 Web 组件或 EJB 组件实现服务接口没有任何区别。
public class
PurchaseOrderServiceBean
implements SessionBean {
...
public String submitPO(PurchaseOrder
po) throws
InvalidPOException,
RemoteException {
//see code for details
}
...
}
代码示例 5:订单服务端点的实现类
因此端点需要一个 WSDL 文件、一个 Java 接口、一个具有 submitPO
方法(接收具有 PurchaseOrder 结构中定义的结构和类型的 PurchaseOrder)的 Java 实现类,还可能包括一些 helper Java 类来表示 PurchaseOrder。这就像许多 Java 程序员使用的普通接口,因为它类似于传递对象,为众多开发者提供了一个方便的策略。这种策略非常适用于文档结构明确定义,并需要根据上述结构生成服务的情况。例如,根据垂直行业标准发票文档生成一种服务。这种策略的优势在于能够充分利用 JAX-RPC 的自动结构验证。例如,收到 PurchaseOrder 文档之后,JAX-RPC 运行时会自动根据 PurchaseOrder 结构验证传入的 PurchaseOrder,然后再将订单分发到服务实现类,这样可以减少开发者的某些编码工作。同时,这种策略的优势还在于可以自动绑定到由 JAX-RPC 提供的 Java 对象。例如,在上述订单示例中,XML PurchaseOrder 文档被自动绑定到 submitPO 方法签名中的 PurchaseOrder.java 对象。这样,开发者就可以无需通过编写代码来创建基于收到的 XML 文档的 Java 对象了。
请注意,文档的描述与服务接口紧密耦合。因此,如果试图更改文档定义,则需要更改接口或 WSDL。这会影响接口的演变。使用这种策略,很难通过接收多种类型文档的方法来生成更为通用的服务,因为服务操作被紧密地绑定到了单个文档的结构上。在已知所交换的文档类型的情况下(例如垂直行业定义的结构),则在接口中使用结构定义类型的解决方案会非常有效。这也是一种常用的解决方案。
参考资料
有关本主题的详细信息,请参见下列资源:
© Sun Microsystems 2005。Java BluePrints Solutions Catalog 中的所有内容受版权保护,未经 Sun Microsystems 的明确书面许可,不得在其他产品中发布。