改善 J2EE 与 .NET 之间的互操作性,第 2 部分

40 篇文章 0 订阅

引言

该系列文章的第 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 接受 ProductSystem.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 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 元素的无界序列。ProductsArrayList 已经被翻译成了 XML Schema 定义中的匿名元素序列。这是所期望的;但是,它也引发了两个问题。首先,其它的 Collection 类型也将被翻译成 xsd:ArrayOfAnyType。因此,在另一个平台上的 SOAP 工具包如何确定将它映射成哪种 Collection 类型?

其次,当没有指定类型的时候 xsd:anyType 就是缺省的类型。清单 2 中的第 11 行是需要的,因为 Collection 中的对象是通用的参照——在运行之前并不知道类型。当在另一个平台上的 SOAP 工具包接收到序列化的对象时问题发生了。您如何找出正确的序列化器来将 XML 载荷反序列化到具体的对象中?

事实上,JAX-RPC 从清单 2xsd: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 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 所示。


清单 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 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:unsignedShortxsd:unsignedByte)是典型的例子。在 .NET 中,uint、ulong、ushortubyte 类型直接地映射到那些 xsd 类型中,但是 Java 语言没有无符号的数值类型。考虑到互操作性,不要公布那些在 Web 服务方法中的数值类型。取而代之,您可以创建封装器方法来公布并传递那些数值类型,如 xsd:string(使用 C# 中的 System.Convert.ToString)。

对于 xsd:decimal、xsd:doublexsd:float 类型,每个平台可能有不同的精度支持。结果,如果您没有在整合之后测试 Web 服务那么可能会降低精度。

无论数据类型是数值类型还是引用类型,信息传递的一方都可能出现问题。数值类型的对象位于栈中,但是引用类型的对象位于堆中。这意味着引用类型可能有空指针,但是数值类型不能有空值。如果 XSD 类型在一种语言中被映射成了数值类型,而在另一种语言中被映射成了引用类型,那么这可能导致问题的出现。例如 xsd:dateTime 被映射成了 System.DateTime,这是 C# 中的数值类型。它也被映射成了 java.util.Calendar,这是 Java 中的引用类型。事实上,java.util.Datejava.util.Calendar 都是引用类型。在 Java 中,当引用类型没有引用任何对象时将其赋空值,这是公共的操作。然而,如果 .NET Web 服务从 Java 客户端接收到数值类型为空值的数据时,将抛出 System.FormatException。为了避免这个问题的出现,您可以定义复合类型来封装数值类型,并将这个复合类型置为空来表示空引用。





回页首


结束语

在本文中,您可以看到由于使用某些数据类型而产生的一些互操作性的问题。为了在使用数据类型时能够达到更好的互操作性,一般的规则是:

  • 尽量多地使用简单数据类型。完全避免使用那些异样的复合类型,如 ArrayListTree,甚至公共的 Hashtable
  • 即使简单的数组通常都具有非常好的同 Web 服务的交互性,注意数组中的内容,确保数组中的元素在每个平台上的含义都是相同的,并且避免发出含有空元素的数组。
  • 注意每个平台都是如何实现一些本地原始类型的,如 floatdoubledates 和 times
在该系列文章中的下一部分,我将研究在 Web 服务互操作性上的命名空间所产生的影响。



回页首


参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值