改善J2EE与.NET之间的互操作性(二)

日期: 2008-11-03 作者:Wangming Ye 来源:TechTarget中国 英文

  该系列文章中的第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数据类型所共享的一对一的映射,所以转译问题导致了信息的丢失或精度的降低。

  在Web服务方法签名中的复合数据类型集

  集合对象可能包括任何数据类型的元素。因此,许多人把它们看作弱类型的数据结构。这使得它们成为非常好的编程工具。在面向对象的编程中,有大量的集合类型库。例如,在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类型的新对象。

  清单1. 在C#中Web服务的详细目录
  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
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服务,公布前端方法:

  清单4. 公布简单数组Product[]的前端方法
   [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服务方法。

  清单6. 返回含有空元素的数组的Java方法
 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 >
  <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所示。

  清单8. 含有数组及其输入输出签名的Java方法
 public String[] processArray(String[] args) {
  //do something to the input array and return it back to the client
  return args;
 }
 
  这次,Web服务方法将数组作为输入,处理它,并将这个数组返回到客户端。假设.NET客户端发出含有空元素的数组,代码如清单9所示。

  清单9. .NET客户端发出含有空元素的数组
  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请求。

  清单10. .NET客户端发出的SOAP请求

  <soap:Body>
  <processArray >
  <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服务互操作性上的命名空间所产生的影响。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐