IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  WebSphere  >

IBM WebSphere 开发者技术期刊: Web 服务中关于继承的考虑

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Greg Flurry, 高级技术人员, IBM

2005 年 1 月 01 日

实际的解决方案开始利用面向服务的体系结构原理,包括 Web 服务的使用在内。这些解决方案经常结合那些利用继承的数据模型。本文研究了 IBM 的 Web 服务相关工具及运行时上下文中的继承。

引言

研究一下图 1 中所示的数据模型。这个数据模型描述了一个相当简单的定制汽车服务的概念。该模型使用类型继承来利用定制中项目间的相似性,当然照样允许有差别。继承是一种普遍需求,因此是面向对象环境中的公共性能,如 Java™,在 XML 模式这样的数据描述型语言中也是如此。近来面向服务约定的经历已经证明了几乎所有的这些约定中都需要继承,因此该主题变得越发有趣。


图 1. 定制数据模型
图 1. 定制数据模型

要在含有 Web 服务的面向服务解决方案中使用数据模型,可以使用自底向上或自顶向下的开发方法。

在自底向上的 Web 服务开发中,开发人员将:

  1. 创建一组用于 Web 服务接口的 JavaBean™,这些 bean 可以定义数据模型,或利用工具从数据模型中派生。有时称这些 JavaBean 为数据转换对象(data transfer object,DTO)。
  2. 创建 Java 类,用来定义使用公共业务方法的 Web 服务,这些方法会成为 Web 服务操作。
  3. 利用 Java2WSDL 工具创建 Web 服务实现,将 Java 类作为 Web 服务。这意味着要创建许多服务器端的构件,如 WSDL 文档。使用如那些 IBM WebSphere® Studio 系列的开发工具时,通常不直接使用 Java2WSDL,而是通过向导对其进行封装。

在自顶向下 Web 服务开发中,开发人员将:

  1. 为接口中用到的 DTO 创建(或确定)数据模型。为了用于 Web 服务开发,使用 XML 模式来描述数据模型。
  2. 创建 WSDL 文档,定义 Web 服务操作,这些操作要用到 DTO 的某些子集。
  3. 利用 WSDL2Java 工具创建服务实现框架,及其他服务器端构件。使用如那些 IBM WebSphere Studio 家族系列的开发工具时,通常通过向导来封装 WSDL2Java。

Web 服务开发的自底向上及自顶向下方法都有其各自的优点和缺点。本文深入研究了与 IBM 的 WebSphere JAX-RPC 兼容的 Web 服务工具和运行时的行为,因为它与数据模型中的继承有关。这些研究将会帮助您为自己的面向服务解决方案做出更合理的选择。

本文将始终使用上面提到的汽车服务实例。





回页首


自底向上 Web 服务开发中的继承

现在研究自底向上开发实例中的继承。清单 1 显示了一组 JavaBean,描述了图 1 中的数据模型。


清单 1. 描述数据模型的 Java 类层次结构
				
        
package com.bat.order;
public class Order {
	protected float total;
	protected float tax;
	protected float net;
	protected Item[] item;
	/* getters & setters omitted */
}
public class Item {
	protected String name;
	protected String description;
	protected String sku;
	protected float price;
	/* getters & setters omitted */
}
public class Labor extends Item {
	protected String skill;
	protected float hours;
	protected float rate;
	/* getters & setters omitted */
}
public class Part extends Item {
	String vehicleCode;
	/* getters & setters omitted */
}
public class Wiper extends Part {
	int length;
	/* getters & setters omitted */
}
public class Carburetor extends Part {
	protected int barrels;
	/* getters & setters omitted */
}
      

清单 2 显示了一个 Java 类,该类有一个以特定 ID 返回订单的方法。在实际环境中,实现要访问数据库。在这个简单的实例中,实现返回一个硬编码的订单,其中包含 WiperLabor 项。


清单 2. 服务类
				
        
package com.bat.order;
public class OrderService {
	public Order getOrder(int id) {
		Order order = new Order();
		Item[] items = new Item[2];
		order.setItem(items);
		Wiper w = new Wiper();
		w.setDescription("fancy double bladed wiper insert");
		w.setName("XYZ wiper insert");
		w.setPrice(5.67f);
		w.setSku("4320");
		w.setVehicleCode("X43B");
		items[0] = w;
		Labor l = new Labor();
		l.setDescription("install wiper insert");
		l.setHours(0.1f);
		l.setName("ServCo wiper insert installation");
		l.setRate(55.00f);
		l.setSkill("A");
		l.setSku("3451");
		items[1] = l;
		return order;
	}
}
      

我们使用 WebSphere Studio Application Developer 版本 5.1.2(以下简称为 Application Developer)Web 服务向导从 OrderService 创建 Web 服务。向导生成 WSDL 文件,该文件定义了 Web 服务接口。利用 Application Developer Web 服务客户端向导,生成客户端构件,指向 WSDL 文档。生成的构件包含 JAX-RPC 兼容的存根、封装了存根的代理(为开发人员提供方便)以及一组类,这些类与 WSDL 模式中定义的复杂类型相对应。我们可以创建测试客户端,它使用代理发送请求消息到服务实现,进行定制。

清单 3 显示了响应消息。我们可以看到订单的结构( <getOrderReturn> 元素)及两个条目( <item> 元素)。即使服务实现实际上返回的是 Item 子类(在该实例中,指的是 Wiper 及 Labor,他们实际上是 Part 的子类),消息中返回的类型隐式的为 Item,且消息只能包含与 Item 对应的字段。特定于 Part(vehicleCode)、Wiper(长度)及 Labor(速度、技巧、时间)的信息都已经丢失。


清单 3. 自底向上 Web 服务开发的响应
				
        
<?xml version="1.0" encoding="UTF-8"?>
   <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 		 			
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance">
      <soapenv:Body>
         <getOrderResponse xmlns="http://order.bat.com">
            <getOrderReturn>
               <net>11.17</net>
               <tax>1.12</tax>
               <total>12.29</total>
               <item>
                  <description>fancy double bladed wiper insert</description>
                  <name>XYZ wiper insert</name>
                  <price>5.67</price>
                  <sku>4320</sku>
               </item>
               <item>
                  <description>install wiper insert</description>
                  <name>ServCo wiper insert installation</name>
                  <price>0.0</price>
                  <sku>3451</sku>
               </item>
            </getOrderReturn>
         </getOrderResponse>
      </soapenv:Body>
   </soapenv:Envelope>
      

要探究原因,我们研究一下为 Web 服务所生成的 WSDL。清单 4 显示了 WSDL 类型定义中的模式。注意模式中有 Order 及 Item 类的类型定义,但是没有 Item 的子类的类型定义。因此,Web 服务实现可以使用子类,但由于他们没有出现在接口定义中,所有不能在消息中发送他们。更进一步,由于客户端构件只是从 WSDL 模式中派生的,即使是子类也可以在消息中返回,由于客户端没有可用的 Item 子类,客户端用他们做不了任何事情。


清单 4. WSDL 类型定义
				
        
<wsdl:types>
<schema elementFormDefault="qualified" targetNamespace="http://order.bat.com" 
xmlns="http://www.w3.org/2001/XMLSchema" 	
xmlns:impl="http://order.bat.com" 
xmlns:intf="http://order.bat.com" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <element name="getOrder">
    <complexType>
     <sequence>
      <element name="id" type="xsd:int"/>
     </sequence>
    </complexType>
   </element>
   <complexType name="Order">
    <sequence>
     <element name="net" type="xsd:float"/>
     <element name="tax" type="xsd:float"/>
     <element name="total" type="xsd:float"/>
     <element maxOccurs="unbounded" name="item" nillable="true" 
    type="impl:Item"/>
    </sequence>
   </complexType>
   <complexType name="Item">
    <sequence>
     <element name="description" nillable="true" type="xsd:string"/>
     <element name="name" nillable="true" type="xsd:string"/>
     <element name="price" type="xsd:float"/>
     <element name="sku" nillable="true" type="xsd:string"/>
    </sequence>
   </complexType>
   <element name="getOrderResponse">
    <complexType>
     <sequence>
      <element name="getOrderReturn" nillable="true" 
    type="impl:Order"/>
     </sequence>
    </complexType>
   </element>
  </schema>
 </wsdl:types>
      

为何模式中没有子类?Java 中的 Web 服务标准,JAX-RPC(参阅 参考资料),依据章节 5.4 支持继承。模式中没有子类是因为在 Web 服务接口中没有直接或间接引用他们,接口中只引用了 Order,Order 只引用了 Item,层次结构中的其他成员都是“不可见的”,因为根本就没有引用他们。

在文章 Web 服务值类型继承和互操作性中,Kyle Brown 在研究这个问题的时候给出了一些解决方案:可以添加额外的引用不可见类的方法使他们可见,或利用 Java2WSDL 直接、显式地添加丢失的类。在实际解决方案开发中这些方法似乎都不令人满意,在这些开发中,接口就是协议(在协议中不应该有“伪造”的东西),利用 Application Developer 向导可以节省大量的开发时间。

自底向上 Web 服务开发的结论是利用继承会出现问题。开发人员必须注意确保服务器端分层数据模型要完全展现在 Web 服务接口定义中。





回页首


继承及自顶向下的 Web 服务开发

近期在与客户的面向服务约定方面的经验表明自顶向下开发比自底向上开发使用得要频繁一些,并且我们希望这种情况继续下去,作为附加的 XML 标准显现出来,并在 Web 服务接口定义中使用他们。

在这些约定中,类型层次在解决方案中扮演了一个非常重要的角色,它使我们认识到当需要继承时,自顶向下开发要比自底向上开发更有优越性。举例说明,清单 5 显示了添加到清单 3 模式中的附加复杂类型定义,附加的定义与图 1 的子类型相对应,Application Developer 向导没有生成这些子类型,使用标准 XML 模式扩展元素定义 Item 的子类型。不需要修改 WSDL 文件中的其他元素。


清单 5. 子类的模式
				
        
   <complexType name="Part">
   	<complexContent>
   	  <extension base="impl:Item">
   	    <sequence>
   		<element name="vehicleCode" type="xsd:string"></element>
          </sequence>
   	  </extension>
   	</complexContent>
   </complexType>
   <complexType name="Labor">
   	<complexContent>
   	  <extension base="impl:Item">
   	    <sequence>
   		<element name="skill" type="xsd:string"></element>
   		<element name="hours" type="xsd:float"></element>
   		<element name="rate" type="xsd:float"></element>
   	    </sequence>
   	  </extension>
   	</complexContent>
   </complexType>
   <complexType name="Wiper">
   	<complexContent>
   	  <extension base="impl:Part">
   	    <sequence>
   		<element name="length" type="xsd:int"></element>
   	    </sequence>
   	  </extension>
   	</complexContent>
   </complexType>
   <complexType name="Carburetor">
   	<complexContent>
   	  <extension base="impl:Part">
   	    <sequence>
   		<element name="barrels" type="xsd:int"></element>
   	    </sequence>
   	  </extension>
   	</complexContent>
   </complexType> 
      

理解这一点很重要:当我们简单的修改嵌入在 WSDL 文档中的现有模式时,数据模型由现有的独立模式定义,而该模式又由手工创建的 WSDL 文档所引用,能很好的运用自顶向下的技术:

  • 我们使用 Application Developer Web 服务向导从增强的 WSDL 生成框架 Java Web 服务。向导生成许多服务器端构件,包括框架及新的有适当服务端点地址的 WSDL 文件。
  • 通过将清单 2 中的服务方法复制到由向导创建的框架中来完成 Web 服务实现。
  • 接下来,从新的 WSDL 文件中生成客户端构件。检查客户端构件,表明 Item 的所有子类都可用于客户端。

利用带新的客户端构件的同一客户端实现请求订单,清单 6 显示了响应结果。这里与清单 3 中的响应有一个很大的不同之处:新的响应包括 Item 子类,已由 Web 服务实现作为订单的一部分。以粗体显示的代码说明响应包含 xsi:type 属性,该属性表明 Item 子类的类型已经包含在响应中。而且,由于响应类型是正确的,所以响应包含 Item 子类的附加元素。


清单 6. 自顶向下 Web 服务开发的响应
				
        
<?xml version="1.0" encoding="UTF-8"?>
   <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance">
      <soapenv:Body>
         <getOrderResponse xmlns="http://order.bat.com">
            <getOrderReturn>
               <net>11.17</net>
               <tax>1.12</tax>
               <total>12.29</total>
               <ns-11:item 
        xsi:type="ns-11:Wiper" xmlns:ns-11="http://order.bat.com">
                  <ns-11:description>fancy double bladed wiper insert</ns-11:description>
                  <ns-11:name>XYZ wiper insert</ns-11:name>
                  <ns-11:price>5.67</ns-11:price>
                  <ns-11:sku>4320</ns-11:sku>
                  <ns-11:
        vehicleCode>X43B</ns-11:vehicleCode>
                  <ns-11:
        length>0</ns-11:length>
               </ns-11:item>
               <ns-11:item 
        xsi:type="ns-11:Labor" xmlns:ns-11="http://order.bat.com">
                  <ns-11:description>install wiper insert</ns-11:description>
                  <ns-11:name>ServCo wiper insert installation</ns-11:name>
                  <ns-11:price>0.0</ns-11:price>
                  <ns-11:sku>3451</ns-11:sku>
                  <ns-11:
        skill>A</ns-11:skill>
                  <ns-11:
        hours>0.1</ns-11:hours>
                  <ns-11:
        rate>55.0</ns-11:rate>
               </ns-11:item>
            </getOrderReturn>
         </getOrderResponse>
      </soapenv:Body>
   </soapenv:Envelope>
      

清单 7 显示了一个新客户端实现,该实现测试返回项的类型。这是一种可以使客户端能确定他接收到的是类层次中的什么成员的方法,并相应处理子类中的附加信息。运行客户端表明客户端可以接收由 Web 服务返回的订单的任何 Item 的子类,演示了客户端 Web 服务运行时使用 xsi:type 属性实例化发送给客户端应用程序的适当的子类。


清单 7. 子类的客户端测试
				
        
public static void main(String[] args) {
	OrderServiceProxy proxy = new OrderServiceProxy();
	Order order = null;
	try {
		order = proxy.getOrder(1);
	} catch (RemoteException e) {
		e.printStackTrace();
	}	
	Item[] items = order.getItem();
	for (int i=0; i<items.length; i++){
		Class iClass = items[i].getClass();
		if (iClass.getSuperclass().equals(Part.class)) {
			if (iClass.equals(Wiper.class)){
			    System.out.println("item["+i+"]="+"Wiper");
			} else if (iClass.equals(Carburetor.class)) {
			    System.out.println("item["+i+"]="+"Carburetor");
			}
		} else if (iClass.equals(Labor.class)) {
			System.out.println("item["+i+"]="+"Labor");	
		}
	}
}
      

虽然可能很明显,以下三个额外的观察可能毫无意义:

  • 由于客户端拥有完全的数据模型层次,客户端就有可能在 Web 服务请求中发送层次中的子类。例如,OrderService 可以有为现有订单添加任何 Item 子类的操作。
  • 订单可能是数据模型中的其他层次的根。如果是这样,客户端和服务就可以交换包含 Item 子类订单的子类。
  • 如果自底向上客户端(从 WSDL 中生成,只包括清单 4 中的类型定义)从自顶向下服务实现(从 WSDL 中生成,还包括清单 5 中的类型定义)中接收了如清单 6 所示的响应,客户端应用程序将会发生反串行化错误,这是由于客户端环境中没有适当的类。

使用自顶向下 Web 服务开发的结论是利用继承非常简单及自然。层次数据模型的全部展现是自动的。

这里的警告是适当的。在 Web 服务值类型继承和互操作性中,要注意到 xsi:type“值对象继承扩展机制超出了 WS-I 基本概要的范围,虽然没有明确地将其排除在外”。因此,虽然那篇文章演示了互操作性,但没有保证与其他 Web 服务环境的互操作性,直到 Web 服务互操作性组织(WS-I,参阅 参考资料)涉及这个主题。经验迫使我们不同意广泛的评估,这样的冒险与回报不符,但是我们同意在 case-by-case 基础上评估风险。





回页首


结束语

本文介绍了一种自顶向下的 Web 服务开发方法。IBM WebSphere JAX-RPC 兼容的工具和运行时为数据模型层次提供了很好的支持。从定义了数据模型层次的 XML 模式开始,可以使 Web 服务开发人员以简单自然的方式利用服务和客户端实现的继承。然而,开发人员必须能够估算出这种互操作性问题所带来的风险。

接下来,我们介绍了自底向上的 Web 服务开发,虽然对 Web 服务开发人员来说也是一种重要的方法,但当数据模型需要继承的时候这种方法可能会出现问题。然而,通过额外的工作可以使自底向上的开发方法支持层次数据模型,因而不会被自动淘汰掉。



参考资料



关于作者

Author photo

Greg Flurry 是 IBM 的企业集成解决方案小组的一名高级技术人员。他的职责包括与面向服务解决方案的客户合作和推进 IBM 的面向服务的产品。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款