提高 J2EE 技术与 .NET 之间的互操作性,第 3 部分

40 篇文章 0 订阅

引言

正如 Java 包通常用来保证 Java 类,使其只能存在于不同层次的命名空间中,这样就可避免类、方法等等之间的命名冲突,XML 命名空间也是为相同的目的而服务于 Web 服务。它限定 XML 元素或属性的名字并帮助它们避免命名冲突。XML 命名空间是基于 URL 应当是全局唯一的基础之上的。然而,解释 URL 的方法及在本机代码的映射对于不同的平台来说是不同的。通常这些不同之处是微妙的,但如果开始时不解决这些的差别,到后来有可能会很难解决。

我将在下面的部分当中讨论几个与命名空间有关的互操作性问题,包括:

  • 使用相关的 URI 引用
  • 使用共享通用域名的唯一的 URI
  • 数组类型中的命名空间问题




回页首


在 WSDL 中用相关 URI 引用作为命名空间声明

在命名空间声明中并没有严格禁止相关 URI 引用,但在规范中也没有为它们提供解释。如果 WSDL 文件是从 J2EE Web 服务中生成的,这通常不是一个问题,因为目标命名空间是从 Java 包名字派生而来,并且工具(例如,Java2WSDL)自动将它们与模式联系起来。但是在 Microsoft .NET Web 服务实现当中,如果您允许 .NET 框架生成 WSDL 文件,那么目标命名空间就会直接从您在代码中的定义生成。您可能会经常看到命名空间属性被分配到相关 URI 的情况。清单 1 显示了从库存中取得产品列表的 C# .NET Web 服务代码。


清单 1. 有相关命名空间 URI 的库存 Web 服务

  
[WebService(Namespace="services.inventory")]
  public class GetProductsService: WebService 
  {
	public struct Product {
		public string name;
		public int 	qty;
		public float price;
	}
	[WebMethod]
	[XmlInclude(typeof(Product))]
	public Product[] listProducts()
	{
		Product[] products = 
		getInventory();  // getInventory() is a private method 
to retrieve all products
		return products;
	}
   }


清单 1 中,Namespace="services.inventory" 属性在 WSDL 文件中的结果是 targetNamespace="services.inventory"。结果,所有在本地定义的元素、类型及属性均被映射到命名空间的相关 URI services.inventory 之下。以下显示了 WSDL 文档的模式部分:


清单 2. 生成的 WSDL 文件显示了作为 targetNamespace 的相关 URI 引用


    xmlns:s0="services.inventory"
    <types>
        <s:schema elementFormDefault="qualified"
            targetNamespace="services.inventory" 
xmlns:s="http://www.w3.org/2001/XMLSchema">
            <s:complexType name="ArrayOfProduct">
                <s:sequence>
                    <s:element maxOccurs="unbounded" minOccurs="0"
                        name="Product" type="s0:Product"/>
                </s:sequence>
            </s:complexType>
            <s:complexType name="Product">
                <s:sequence>
                    <s:element maxOccurs="1" minOccurs="0" name="name" 
type="s:string"/>
                    <s:element maxOccurs="1" minOccurs="1" name="qty" 
type="s:int"/>
                    <s:element maxOccurs="1" minOccurs="1" name="price" 
type="s:float"/>
                </s:sequence>
            </s:complexType>
            <s:element name="ArrayOfProduct" nillable="true" 
type="s0:ArrayOfProduct"/>
        </s:schema>
    </types>


elementFormDefault="qualified" 属性确保 targetNamespace 限定包括复杂类型 Product 在内的所有局部声明元素。假设有另一个单位使用相同的相关命名空间实现类似的 Product 类型。就像当使用 IBM® WebSphere® Studio Application Developer Integration Edition(Application Developer)BPEL 设计器从不同的伙伴链接中利用普通复杂类型将 Web 服务集成到业务流程中时使用 wsdl:importxsd:import 一样,从普通模式中将这两个模式导入到 WSDL 文档中。

在本场景中,如果导入的两个模式有相同的目标命名空间,则很有可能发生命名冲突。用于在另一个平台上构建集成的工具必须确保相关 URI 是基于 RFC2396 标准文档树中的基本 URI 的。然而,在 WSDL 文档中基本 URI 并没有定义完善,默认的基本 URI 的解释取决于应用程序。最好的习惯是始终使用它自己的组织域名来确保命名空间唯一。





回页首


共享通用域名的唯一命名空间 URI

有人说命名空间污染是软件工程中最糟糕的污染。每个组织都有不同的命名习惯,所以工具通常生成的 Web 服务存根代码在另一个平台上的 WSDL 命名空间声明中或许有不同的解释。Web Services Interoperability Organization(WS-I)规范已经在消除命名空间声明中的模糊点方面有了重大改进,并且正在向统一命名空间解释方向前进,但其中仍有一些不足之处。

考虑两个假设,小型银行分部及大型银行投资分部的 .NET Web 服务。一个为客户创建校验帐户,另一个创建投资帐户:


清单 3. .NET 中的零售 AccountService



namespace Retail
{
  [WebService(Namespace="http://bigbank.com/retail")]
  public class AccountService: WebService 
  {
	public struct Customer {
		public string name;
		public string address;
	}
	[WebMethod]
	public bool createCheckingAccount(Customer customer)
	{
		return true;
	}
   }
}



清单 4. .NET 中的投资 AccountService



namespace Investment
{
  [WebService(Namespace="http://bigbank.com/investment")]
  public class AccountService: WebService 
  {
	public struct Customer {
		public string name;
		public string address;
		public int collatoral_amt;
	}
	[WebMethod]
	public bool createInvestmentAccount(Customer customer)
	{
		return true;
	}
   }
}


上述两个帐户服务有相同的域,但有不同的分支: http://bigbank.com/retailhttp://bigbank.com/investment。由于不同的需求,零售 AccountService 中有一个比投资 AccountService 中稍有不同的 Customer 复杂类型。两个 AccountService 类文件都命名为 AccountService.asmx。在 .NET 中这不会有问题,而实际上在 .NET 中是完全顺乎其理的,因为两个帐户服务都被限定为不同的 URL,他们都准确的反映出他们的域名及分支名。

现在,如果您准备在 Application Developer 中创建客户端项目并试图集成两个 Web 服务,情况就要有所变化。在 Java 代码中,当创建 Web 服务客户端时,包名是基于命名空间的域名的。http://bigbank.com/retail http://bigbank.com/investment 有相同的域名:http://bigbank.com。因此,生成的复合数据类型及两个 Web 服务的代理将有相同的包名:com.bigbank。因为我们将两个 .NET Web 服务命名为 AccountService.asmx,并且两个不同的 Customer 结构类型有相同的名字,结果很明确:当 Application Developer 生成代理文件时,为 AccountService 客户端生成的存根文件(AccountService.java、AccountServiceLocator.java、AccountServiceSoap.java、AccountServiceSoapProxy.javaAccountServiceSoapStub.java)将重写先前生成的同名文件。根据后期生成的结果,现在只有一个 Customer 复合类型而不是两个。

这个命名冲突是在 .NET 及 Java 技术中命名习惯的不同而导致的。正如您所看到的,命名空间声明中的唯一 URL 仍不能完全避免命名冲突。解决方法是保证每个 Web 服务拥有唯一的域名。上面的两个 AccountService 可以使用 http://retail.bigbank.comhttp://investment.bigbank.com/ 分别作为命名空间限定符,从而使域名唯一。

如果没有经验改变现有 .NET Web 服务中的命名空间声明的话,Application Developer 中的 Web 服务客户端代理生成向导也会提供一个选项来定义由命名空间到包的自定义映射,如图 1 所示。


图 1. 在 Application Developer 中定义由命名空间到包的自定义映射





回页首


命名空间及共享 XSD 模式

在 J2EE 技术及 .NET 中,在众多的 Web 服务中共享 XSD 模式是非常普遍的。实际上,共享 XML 模式是模块化设计及可重用性考虑的最佳实践。XML 标签:importinclude 的使用也正是由于此目的。例如,您可以为货物仓库的 Product 类型设计模式,如清单 5 中所示:


清单 5. Product 类型

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace=
"http://catalog.warehouse.com" xmlns:Product="http://catalog.warehouse.com">
	<complexType name="Product">
		<sequence>
			<element name="_name" type="string"></element>
			<element name="_int" type="int"></element>
		</sequence>
	</complexType>
</schema>


Product 类型被限定于命名空间 http://catalog.warehouse.com 之下。可以为其他 Web 服务将其导入到 WSDL 中管理库存。假设订货部门有 C# 的 Order Web 服务实现,如清单 6 中所示:


清单 6. .NET 中的定购 Service


	[WebService(Namespace="http://order.warehouse.com/service")]
	public class OrderProductService: System.Web.Services.WebService
	{
		[WebMethod]
		[XmlInclude(typeof(Product))]
		public string OrderProducts(Product[] products) 
		{
			int len = products.Length;
			//do the order
			return "Total number of orders processed: " + len;
		}
	}


库存部门使用库存 Web 服务进行重新进货,它必须重用相同的 Product 类型,如清单 7 中所示:


清单 7. .NET 中的库存 Service


	[WebService(Namespace="http://inventory.warehouse.com/service")]
	public class InventoryProductService: System.Web.Services.WebService
	{
		[WebMethod]
		[XmlInclude(typeof(Product))]
		public string RestockProducts(Product[] products) 
		{
			int len = products.Length;
			//add to the inventory
			return "Total number of products added: " + len;
		}
	}


现在有三个命名空间:Product 类型的 http://catalog.warehouse.comOrder Web 服务的 http://order.warehouse.com/service 以及 Inventory Web 服务的 http://inventory.warehouse.com/service。乍一看,似乎没有潜在的命名冲突。依据前两部分:这三个 URI 有足够的资格,每个 Web 服务的域名都是唯一的,甚至 Web 服务类名也是不同的。

但仍然会发生问题。当在清单 6清单 7 中的两个 Web 方法中传递数组时会发生问题。

如果您创建 J2EE 项目集成定购服务及库存服务,并定购一些产品或重新进货某些产品,只有一个 Web 服务会正确执行。由于接收到产品的空数组,所以另一个服务会悄无声息的失败掉,即使 J2EE 客户端确实为两个服务都发送了填充了产品的数组也是如此。为什么会这样?

您应该研究 J2EE 客户端与 .NET Web 服务之间的 SOAP 通信来寻找答案。


清单 8. .NET 定购服务的 SOAP 请求


<soapenv:Body>
     <OrderProduct xmlns="http://order.warehouse.com/service">
          <products>
                <Product>
                    <_name xmlns="http://catalog.warehouse.com">Computer</_name>
                    <_qty xmlns="http://catalog.warehouse.com">10</_qty>
                </Product>
                <Product>
                    <_name xmlns="http://catalog.warehouse.com">Monitor</_name>
                    <_qty xmlns="http://catalog.warehouse.com">20</_qty>
                </Product>
          </products>
     </OrderProduct>
</soapenv:Body>



清单 9. .NET 库存服务的 SOAP 请求



<soapenv:Body>
     <RestockProduct xmlns="http://inventory.warehouse.com/service">
            <products>
                <Product xmlns="http://order.warehouse.com/service">
                    <_name xmlns="http://catalog.warehouse.com">Computer</_name>
                    <_qty xmlns="http://catalog.warehouse.com">10</_qty>
                </Product>
                <Product xmlns="http://order.warehouse.com/service">
                    <_name xmlns="http://catalog.warehouse.com">Monitor</_name>
                    <_qty xmlns="http://catalog.warehouse.com">20</_qty>
                </Product>
            </products>
     </RestockProduct>
</soapenv:Body>


比较清单 8清单 9 中的 orders 数组及 products 数组的 XML 表示。在 Inventory 服务请求中的数组元素由 Order 服务的命名空间 URI 所限定-http://order.warehouse.com/service,所以 Inventory Web 服务不会看到发送给他的任何产品。

那么,问题的根源在哪里呢?问题在于 Application Developer JAX-RPC 工具生成的 .NET WSDL 及帮助类。清单 10 显示了与 ArrayOfProduct[] 有关的定购服务 WSDL:


清单 10. 与 ArrayOfProduct 类型有关的 .NET Order 服务 WSDL 的一部分


xmlns:s1="http://catalog.warehouse.com" 
xmlns:s="http://www.w3.org/2001/XMLSchema" 
xmlns:s0="http://order.warehouse.com/service"
targetNamespace="http://order.warehouse.com/service" 
.......
<types>
  <s:schema elementFormDefault="qualified" targetNamespace=
  "http://order.warehouse.com/service">
     <s:import namespace="http://catalog.warehouse.com" /> 
      .......
     <s:element name="Product" type="s1:Product" /> 
  </s:schema>
  <s:schema elementFormDefault="qualified" targetNamespace=
  "http://catalog.warehouse.com">
     <s:import namespace=
     "http://order.warehouse.com/service" /> 
       <s:complexType name="ArrayOfProduct">
         <s:sequence>
           <s:element minOccurs="0" maxOccurs="unbounded" ref="s0:Product" /> 
         </s:sequence>
       </s:complexType>


ArrayOfProduct 模式位于 targetNamespace http://catalog.warehouse.com 之中,但它的元素引用 s0:Product,其中 s0http://order.warehouse.com/service

回忆当创建 JAX-RPC 客户端存根类时,命名空间 URI 如何映射到 Java 包名。在 com.warehouse.catalog 包下面创建了 ArrayOfProduct 类,帮助类 ArrayOfProduct_Helper 将它的 Product 字段绑定到命名空间 s0,即 http://order.warehouse.com/service 上(参见本文中的清单 11)。

这并不是好消息。InventoryOrder Web 服务的客户端共享 com.warehouse.catalog 包下面的相同的 ArrayOfProduct 类,但是它的类字段却绑定到特定的 Web 服务上。在本例中是 Order 服务。这正是在清单 7 中您可以看到的 SOAP 请求消息,问题的根源就在于此。

在本场景中,没有任何一方违反规范。没有理想的解决方法。首先您必须发现问题,您可以做两件事情来解决这个命名空间绑定冲突的问题:

  • 当创建第一个客户端代理文件时,将 com.warehouse.catalog 重命名为另外一个包名。当生成第二个客户端代理文件时,两个文件将都有正确的命名空间绑定。
  • 当生成客户端代理文件时,使用如图 1 所示相同的技术。为每个 Web 服务将 http://catalog.warehouse.com 映射到唯一的命名空间。

共享 XSD 模式是一个最佳实践,但 Web 服务程序员必须注意诸如此类的潜在的命名空间问题,并且要知道当问题发生时如何去修正它。





回页首


结束语

在本文中讨论了由 XML 命名空间冲突所导致的某些互操作性问题。然而,相互作用的 Web 服务之间的命名空间冲突没有停止于此。仍有许多其他的情况,或是微妙或是稀少,但它会发生。当有大量的 Web 服务部署在大型的公司环境中时,很难修正命名空间冲突问题。当开发 Web 服务时,最好要预见并避免不同平台上潜在的冲突。IBM WebSphere Studio Application Developer Integration Edition 也提供了强大的重置工具。如果在集成期间真的有命名冲突发生,该工具可以帮助您进行重置。

本系列的技巧讨论了大量的重要议题,可以解决跨平台 Web 服务互操作性并提供最佳实践,特别是在 XML Schema 类型的使用、命名空间及 Web 服务接口绑定方面。在 WS-I 成员多年的共同努力下,Web 服务互操作性是可以实现的了。第三方 IDE 工具已经成熟,将能更好的集成。但认为有朝一日只是点击几下鼠标即可完成所有的 Web 服务集成也是不切合实际的,即使通过最成熟及强大的 IDE 工具的帮助也是如此。毕竟,Web 服务是在不同的平台上开发的。即使每个人都依据相同的 WS-I 规范开发,仍然会有不匹配、曲解及不同的习惯。在产品领域,仍有需要被工程师考虑的许多互操作性问题。某些重要的互操作性问题是:

  • 错误处理: 预见不同的错误情况及可以发生且被通信方返回的错误类型很重要,并将那些 wsdl:faults 定义为 WSDL 中 wsdl:operations 的一部分。
  • 安全互操作性: Web 服务调用需要签名及加密(WS-Security)。为 Web 服务执行 vendor-neutral 及 transport-independent 安全措施是 WS-Security 的目标,它也是产品环境中 Web 服务集成的重要一步。

我将在以后的技巧中讨论其他的互操作性问题。





回页首


参考资料


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值