2005 年 2 月 01 日
该系列文章中的第 2 部分探索对于公共的互操作性的需求来源,该需求面向跨平台的 Web 服务集成。跟随 Wangming Ye 分析互操作的失败原因——使用某些数据类型,以及克服错误的方法(如使用集合、数组或原始数据类型)。
该系列文章的第 I 部分讨论了在编码之前设计 Web 服务描述语言(Web Services Description Language,WSDL)和 XML Schema 数据类型(XML Schema data types,XSD)的重要性,完全转换成文档或文字式样的基本原理,以及当开发 Web 服务的时候测试 WS-I Basic Profile 一致性的必要性。本文阐明了数据类型的用法及其对互操作性产生的影响。
Web 服务操作的输入参数及数据类型的返回值对于 Web 服务的互操作性产生非常大的影响。Web 服务用作 XML 文档转换的传送器。当数据对象被放入 Web 服务栈中时,它们被序列化成 XML 数据表示。另一方面,Web 服务栈需要准确地知道如何将那些 XML 数据表示映射到本地应用程序环境的需求中(例如 XML 数据的反序列化)。XML Schema 定义驱动了映射。XSD 的目的是确保发送的类型在其他终端有可复写的版本。但是由于基本技术(企业版 Java 2 平台(Java™ 2 Platform,Enterprise Edition,J2EE)与 Mircosoft® .NET)的实现是不同的,所以 XSD 和那些平台上的本地数据类型之间的映射可能会不同。某些差异可能导致反序列化的失败,而其它的可能导致信息失真。
在接下来的部分中,我将讨论一些有关数据类型的互操作性的问题,例如:
- 提供商用于精确解释 XML Schema 的工具是不存在的,XML Schema 代表弱类型的集合对象并将它们映射成正确的本地数据类型。
- 含有空元素的数组的 XML 表示不同于 .NET 和 IBM® WebSphere®。
- 由于缺乏本地和 XSD 数据类型所共享的一对一的映射,所以转译问题导致了信息的丢失或精度的降低。
![]() ![]() |
![]()
|
集合对象可能包括任何数据类型的元素。因此,许多人把它们看作弱类型的数据结构。这使得它们成为非常好的编程工具。在面向对象的编程中,有大量的集合类型库。例如,在 Java 中存在:
java.util.Hashtable
Vectors
Hashmap
Set
ArrayList
而在 C# 中存在:
System.Collections.Hashtable
SortedList
Queue
Stack
ArrayList
如果在整个 Web 服务中公布了这些集合类型,那么它们可能引发不能被解决的问题。该问题是接收方如何能理解被序列化了的简单对象访问协议(Simple Object Access Protocol,SOAP)消息,这些消息中包含弱类型对象元素及本地数据类型。
即使一些集合类型看上去与某些语言非常相似,例如 C# 中的 System.Collections.ArrayList
及 Java 中的 java.util.ArrayList
,记住集合中的元素是通用的参照。为了准确地解组集合的 XML 表示,客户必须预先了解原始的具体类型。这个任务交给工具包开发人员来解释 Web 服务提供者所发布的 XML Schemas 并将 SOAP 消息映射到本地数据中——不是对于弱类型集合的简单任务。
现在,让我们来看一看 Collection
类型的 XML Schemas 是什么样子。这次,考虑部署在 Microsoft .NET 框架上的 Web 服务。假设 InventoryService
接受 Product
的 System.Collections.ArrayList
作为变量,为 ArrayList
中的每个产品设置新价格(增长了百分之 10),并且返回 System.Collections.ArrayList
类型的新对象。
namespace Inventory { [WebService(Namespace="http://services.inventory")] public class InventoryService: WebService { //increase the product price by 10 percent private static float inc_rate = 0.10F; public struct Product { public string name; public int qty; public float price; } [WebMethod] [XmlInclude(typeof(Product))] public ArrayList updateProductPrice(ArrayList products) { ArrayList newList = new ArrayList(); IEnumerator eList = products.GetEnumerator(); while(eList.MoveNext()) { Product item = (Product)(eList.Current); item.price = item.price * (1 + inc_rate); newList.Add(item); } return newList; } } } |
在 .NET 框架中的 WSDL 引擎生成了用于 Collection
类型、ArrayList
以及 Product
复合类型的如下的 XML Schema:
清单 2. 用于 ArrayList 和 Product 的 XML Schema
1. <types> 2. <s:schema xmlns:s="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://services.inventory"> 3. <s:element name="updateProductPrice"> 4. <s:complexType> 5. <s:sequence> <s:element maxOccurs="1" minOccurs="0" name="products" type="s0:ArrayOfAnyType"/> 6. </s:sequence> 7. </s:complexType> 8. </s:element> 9. <s:complexType name="ArrayOfAnyType"> 10. <s:sequence> 11. <s:element maxOccurs="unbounded" minOccurs="0" name="anyType" nillable="true"/> 12. </s:sequence> 13. </s:complexType> 14. <s:complexType name="Product"> 15. <s:sequence> 16. <s:element maxOccurs="1" minOccurs="0" name="name" type="s:string"/> 17. <s:element maxOccurs="1" minOccurs="1" name="qty" type="s:int"/> 18. <s:element maxOccurs="1" minOccurs="1" name="price" type="s:float"/> 19. </s:sequence> 20. </s:complexType> 21. <s:element name="updateProductPriceResponse"> 22. <s:complexType> 23. <s:sequence> <s:element maxOccurs="1" minOccurs="0" name="updateProductPriceResult" type="s0:ArrayOfAnyType"/> 24. </s:sequence> 25. </s:complexType> 26. </s:element> 27. </s:schema> 28. </types> |
从第 9 行到第 13 行(详见清单 2)定义了复合类型 xsd:ArrayOfAnyType
,连同 anyType 元素的无界序列。Products
的 ArrayList
已经被翻译成了 XML Schema 定义中的匿名元素序列。这是所期望的;但是,它也引发了两个问题。首先,其它的 Collection 类型也将被翻译成 xsd:ArrayOfAnyType
。因此,在另一个平台上的 SOAP 工具包如何确定将它映射成哪种 Collection
类型?
其次,当没有指定类型的时候 xsd:anyType
就是缺省的类型。清单 2 中的第 11 行是需要的,因为 Collection
中的对象是通用的参照——在运行之前并不知道类型。当在另一个平台上的 SOAP 工具包接收到序列化的对象时问题发生了。您如何找出正确的序列化器来将 XML 载荷反序列化到具体的对象中?
事实上,JAX-RPC 从清单 2 的 xsd:ArrayOfAnyType
schema 中生成了如下的帮助类。
清单 3. 用于 xsd:ArrayOfAnyType schema 的作为结果的帮助类
public class ArrayOfAnyType implements java.io.Serializable { private java.lang.Object[] anyType; <!-- The setter, getter, equals() and hashCode() methods --> } |
从清单 3 中,您可以看到 xsd:ArrayOfAnyType
schema 的不明确性已经导致 JAX-RPC 工具生成了帮助类。该帮助类将通用的 java.lang.Object[]
数组作为它的私有字段,取代了具体的 Product
数组。
为了消除这种不明确性,您可以使用 ArrayOfRealType 来代替 xsd:ArrayOfAnyType
。您应当仅公布具体类型的简单数组(也就是 Product[]
),将其作为 Web 服务方法的签名。
对于清单 1 中的 Web 服务,公布前端方法:
[WebMethod] [XmlInclude(typeof(Product))] public Product[] updateProductPriceFacade(Product[] products) { ArrayList alist = new ArrayList(); IEnumerator it = products.GetEnumerator(); while (it.MoveNext()) alist.Add((Product)(it.Current)); alist = updateProductPrice(alist); Product[] outArray = (Product[])alist.ToArray(typeof(Product)); return outArray; } |
对于输入输出消息部分的新 schemas 是:
清单 5. 对于清单 4 中的新的 Web 服务的 XML Schema
1. <s:element name="updateProductPriceFacade"> 2. <s:complexType> 3. <s:sequence> 4. <s:element minOccurs="0" maxOccurs="1" name="products" type="s0:ArrayOfProduct" /> 5. </s:sequence> 6. </s:complexType> 7. </s:element> 8. <s:complexType name="ArrayOfProduct"> 9. <s:sequence> 10. <s:element minOccurs="0" maxOccurs="unbounded" name="Product" type="s0:Product" /> 11. </s:sequence> 12. </s:complexType> 13. <s:element name="updateProductPriceFacadeResponse"> 14. <s:complexType> 15. <s:sequence> 16. <s:element minOccurs="0" maxOccurs="1" name="updateProductPriceFacadeResult" type="s0:ArrayOfProduct" /> 17. </s:sequence> 18. </s:complexType> 19. </s:element> |
从第 8 行到第 12 行,创建 xsd:ArrayOfProduct
schema 来表示具体的 Product
数组。在 schema 中没有出现不确定的内容。所以,最终 Web 服务客户端在反序列化 Products
数组的过程中没有遇到问题。
![]() ![]() |
![]()
|
含有空元素的数组的 XML 表示不同于 .NET 和 WebSphere。考虑清单 6 中所示的 Java Web 服务方法。
public String[] returnArrayWithNull() { String[] s = new String[3]; s[0] = "ABC"; s[1] = null; s[2] = "XYZ"; return s; } |
这里的 String 元素 s[1]
被赋为空值。当 .NET 客户端调用这个部署到 WebSphere 平台上的 Web 服务方法的时候,该 String 数组被序列化成:
清单 7. 来源于 WebSphere 的 Web 服务响应消息
<soapenv:Body> <returnArrayWithNullResponse xmlns="http://array.test"> <returnArrayWithNullReturn>ABC</returnArrayWithNullReturn> <returnArrayWithNullReturn xsi:nil="true"/> <returnArrayWithNullReturn>XYZ</returnArrayWithNullReturn> </returnEmptyStringResponse> </soapenv:Body> |
数组中的第二个元素是设置 xsi:nil="true"
。这在 Java 中是非常有用的;Java 客户端可以正确地将它反序列化成空的 String
值,该值是数组中的第二个元素。然而,.NET 客户端将其反序列化成长度为 0 的字符串而不是空的字符串。长度为 0
和 空
在面向对象的编程语言中是完全不同的概念。
现在,考虑另一个部署在 WebSphere 上的 Web 服务方法,如清单 8 所示。
public String[] processArray(String[] args) { //do something to the input array and return it back to the client return args; } |
这次,Web 服务方法将数组作为输入,处理它,并将这个数组返回到客户端。假设 .NET 客户端发出含有空元素的数组,代码如清单 9 所示。
TestArrayService proxy = new TestArrayService(); string[] s = new string[3]; s[0] = "abc"; s[1] = null; s[2] = "xyz"; // Console.WriteLine("the length of the input array = " + s.GetLength(0)); string[] ret = proxy.processArray(s); // Console.WriteLine("the length of the output array = " + ret.GetLength(0)); |
清单 10 展示了来源于 .NET 客户端的 SOAP 请求。
<soap:Body> <processArray xmlns="http://array.test"> <args>abc</args> <args>xyz</args> </processArray> </soap:Body> |
.NET 客户端发出的 SOAP 请求省略了空元素 s[1]
。结果,返回的数组的长度不再与原始数组的长度一致。如果这个数组的长度或者元素的索引对于客户端的逻辑来说是重要的,那么客户端将会失败。
最佳的实例不是将含有空元素的数组传递到 Web 服务的客户端及服务器上。
![]() ![]() |
![]()
|
XML Schema 通过提供了大量的类型模型来减弱了互操作性。您可以构建 WSDL 消息及操作,因为 XML Schema 能够识别 Web 服务所使用的特定的数据类型。XSD 提供了大量的类型以及简单的结构。但是,每种编程语言都有一套自己的本地数据类型。本地数据类型与 XSD 数据类型之间的一对一的映射是不存在的。因此,在翻译过程中可能丢失信息,或者接收端不可能生成某些本地数据类型的映射。
无符号的数值类型(如 xsd:unsignedInt、xsd:unsignedLong、xsd:unsignedShort
和 xsd:unsignedByte
)是典型的例子。在 .NET 中,uint、ulong、ushort
和 ubyte
类型直接地映射到那些 xsd
类型中,但是 Java 语言没有无符号的数值类型。考虑到互操作性,不要公布那些在 Web 服务方法中的数值类型。取而代之,您可以创建封装器方法来公布并传递那些数值类型,如 xsd:string(使用 C# 中的 System.Convert.ToString
)。
对于 xsd:decimal、xsd:double
和 xsd:float
类型,每个平台可能有不同的精度支持。结果,如果您没有在整合之后测试 Web 服务那么可能会降低精度。
无论数据类型是数值类型还是引用类型,信息传递的一方都可能出现问题。数值类型的对象位于栈中,但是引用类型的对象位于堆中。这意味着引用类型可能有空指针,但是数值类型不能有空值。如果 XSD 类型在一种语言中被映射成了数值类型,而在另一种语言中被映射成了引用类型,那么这可能导致问题的出现。例如 xsd:dateTime
被映射成了 System.DateTime
,这是 C# 中的数值类型。它也被映射成了 java.util.Calendar
,这是 Java 中的引用类型。事实上,java.util.Date
和 java.util.Calendar
都是引用类型。在 Java 中,当引用类型没有引用任何对象时将其赋空值,这是公共的操作。然而,如果 .NET Web 服务从 Java 客户端接收到数值类型为空值的数据时,将抛出 System.FormatException
。为了避免这个问题的出现,您可以定义复合类型来封装数值类型,并将这个复合类型置为空来表示空引用。
![]() ![]() |
![]()
|
在本文中,您可以看到由于使用某些数据类型而产生的一些互操作性的问题。为了在使用数据类型时能够达到更好的互操作性,一般的规则是:
- 尽量多地使用简单数据类型。完全避免使用那些异样的复合类型,如
ArrayList
、Tree
,甚至公共的Hashtable
。 - 即使简单的数组通常都具有非常好的同 Web 服务的交互性,注意数组中的内容,确保数组中的元素在每个平台上的含义都是相同的,并且避免发出含有空元素的数组。
- 注意每个平台都是如何实现一些本地原始类型的,如
float
、double
和dates 和 times
。
在该系列文章中的下一部分,我将研究在 Web 服务互操作性上的命名空间所产生的影响。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- 查看该系列中的其它技巧:
- "Web 服务编程技巧与窍门: 提高 J2EE 技术和 .NET 之间的互操作性,第 1 部分"(developerWorks,2004 年 12 月)
- 阅读 WSDL、SOAP 及其它标准的相关规范:
- 在文章 "我应该采用哪一种 WSDL 样式?" 中获取指导,关于确定哪个 WSDL 绑定的式样或用法是合适的,该文章由 Russell Butek 所著(developerWorks,2003 年 10 月)。
- 了解 WS-I Test Tools 的体系结构及功能的概况,见文章 "了解 WS-I 测试工具",由 Peter Brittenham 所著(developerWorks,2003 年 11 月)。
- 请见指南 "结合 Java 技术使用 WS-I 测试工具",由 Peter Brittenham 所著,关于使用 WS-I 测试工具的 Java 版本的循序渐进的指导(developerWorks,2003 年 11 月)。
- 了解更多的关于 Web Services Interoperability Organization 以及 IBM 对于互操作性的委托。
- 想了解更多内容吗?developerWorks Web Services 及 SOA 专区 中有上百篇信息文章,及关于如何开发 Web 服务应用程序的入门级、中级和高级的指南。
![]() | ||
| ![]() | Wangming Ye 是 IBM 认证企业的开发人员和 SUN 认证的 J2EE 技术企业架构师。他最初在 Transarc 公司(后并入 IBM)的 DCE/DFS 部门做开发人员,后来成为 WebSphere Edge Server 开发小组 的 WebSphere Content Distribution Framework 的主要开发人员。目前他在 IBM 商业伙伴技术支持组织的 Websphere Competency Center 为 Websphere 商业伙伴提供技术支持。您可以通过 yme@us.ibm.com 与他联系。 |