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

developerWorks 中国  >  WebSphere  >

Web 服务值类型的继承和互操作性

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Kyle Brown (brownkyl@us.ibm.com), 高级技术人员, IBM Software Services for WebSphere

2004 年 2 月 01 日

虽然继承在 Java 程序设计中是平常的,但是当您在 Web 服务的上下文中考虑继承时它可能会引起麻烦。本文讨论值类型在 XML Schema 中是如何继承的、如何把它应用在从 Java 对象到 WSDL 的映射中、以及 WebSphere 工具和运行时如何处理这个问题。

虽然继承在 Java程序设计中是平常的,但是当您在 Web服务的上下文中考虑继承时它可能会引起麻烦。本文讨论值类型在 XML Schema中是如何继承的、如何把它应用在从 Java 对象到 WSDL 的映射中、以及 WebSphere 工具和运行时如何处理这个问题。

引言


继承和多态性是面向对象(object-oriented,OO)程序设计的基础。没有人会怀疑二者的重要性。然而,当我们超出面向对象开发的范围,展望组件开发特别是面向服务的体系结构(Services Oriented Architecture,SOA)中的服务开发时,事情将变得更为复杂。虽然服务看起来(从表面上)是非常大粒度的分布式对象,然而,它们实际上可以用面向对象或非面向对象语言通过几种方式中的任何一种来实现。

这个事实的存在使得继承的讨论通常都没有进入 Web 服务的讨论范围。同样地,WSDL 1.1甚至没有包含一种机制来提供接口继承。它曾经允许运算符重载,但是在新的 WSDL 1.2 规范中,考虑到结构更好的 protType继承机制,即便运算符重载也取消了。

尽管 WSDL 端口类型的继承是活跃的讨论主题,但是让人感到奇怪的是,作为WSDL 端口类型的方法参数的 WSDL 类型中的继承却并非如此。这种沉默是暂时的,因为 XML Schema中有一种简单的机制可以用来达到与实现继承同样的目的:扩展机制。

在 W3C XML Schema推荐标准的引言部分(开始)描述了扩展机制。您可以首先在 XML Schema 中定义一个 complexType 元素,然后使用扩展标签声明另一个 complexType 元素来扩展该“基”类型。例如,下面来自 XML Schema推荐标准的示例展示了如何做到这一点:


<complexType name="Address">
  <sequence>
   <element name="name"   type="string"/>
   <element name="street" type="string"/>
   <element name="city"   type="string"/>
  </sequence>
 </complexType>
 <complexType name="USAddress">
  <complexContent>
   <extension base="ipo:Address">
    <sequence>
     <element name="state" type="ipo:USState"/>
     <element name="zip"   type="positiveInteger"/>
    </sequence>
   </extension>
  </complexContent>
 </complexType>
                





回页首


JAX-RPC规范中的继承


假定 XML Schema允许将这样一种简单的机制用于值类型继承,您将会期望不同的 Web服务工具和规范在合适的地方利用这种机制。而 JAX-RPC作为其中的一种就允许这样做。JAX-RPC 规范的第 5.4.1节描述了符合 JAX-RPC 的 Web 服务实现应该如何使用 XML Schema扩展构造来使 WSDL 中的继承值对象关联起来。

特别地,第5.4 节规定(当讨论作为服务端点接口(Service Endpoint Interface)中的方法参数和返回类型的值类型时): [一个] Java类可以实现任何 Java 接口(除了 java.rmi.Remote 接口)或扩展另一个 Java 类。”

接下来,在 5.4.1节中,该规范然后描述应该如何将这些继承 Java 类映射到 XML Schema 类型: “通过利用扩展机制派生xsd:complexType 类型来映射 Java 的继承。”

该规范接下去提供了一个关于值类型继承的简单示例,展示了如何采用与前面来自 XML Schema推荐标准的示例完全一样的方法来使用扩展标签。这意味着符合 JAX-RPC的实现必须支持这种方法。正如我们所期望的,WebSphere Studio Application Developer Version 5.1(并且通过扩展 WebSphere Application Server V5.02)完全支持这种方法。然而如下所示,需要完成少量的工作来演示这种支持:





回页首



假设我们想要构建一个简单的示例,在这个示例中您有两个关联的类:Employee 和 SalariedEmployee。基类 Employee 如下所示:

public class Employee implements java.io.Serializable {
     private String name;
     public String getName() {
          return name;
     }
     public void setName(String nm){
          name = nm;
     }
}
                

子类 SalariedEmployee 如下所示:


public class SalariedEmployee extends Employee {
 private int salary;
 public int getSalary() {
  return salary;
 }
 public void setSalary(int i) {
  salary = i;
 }
}
                

现在,假设我们创建一个名为 EmployeeFactory 的 JavaBean 来实现我们的 Web 服务,它具有以下方法:

public String deriveInfoFor(Employee e) {
 String output = "Name="+ e.getName();
 if (e instanceof SalariedEmployee) {
  output+= " Person is a SalariedEmployee ";
  output+= ((SalariedEmployee)e).getSalary();
 }
 else output+= "Person is a Regular Employee ";
 return output;
} 
                

然后我们必须创建一个服务端点接口(Service Endpoint Interface,SEI)来满足 JAX-RPC 规范的需求:

public interface EmployeeFactoryIF extends java.rmi.Remote{
 public abstract String deriveInfoFor(Employee e);
}
                

接下来,运行 Web 服务向导来从 JavaBean 实现和 SEI 创建一个 Web 服务。同时假设我们指示 Web Service 向导生成 Java代理类,用于为 Web服务提供客户端接口。该向导将创建所有适当的部署描述符和绑定文件。最后,在新创建的 Web 服务客户端项目中,我们添加了一个简单的测试类:

public class EmployeeTester {
 public static void main(String[] args) {
  try {
   java.rmi.Remote stub = createProxy();
   EmployeeFactory simpleWSTest = (EmployeeFactory)stub;
   SalariedEmployee fred = new SalariedEmployee();
   fred.setName("Fred");
   fred.setSalary(1000);
   System.out.println("-------Salaried Test---------");
   System.out.println(simpleWSTest.deriveInfoFor(fred));
   System.out.println("-------Regular Test----------");
   Employee bob = new Employee();
   bob.setName("Bob");
   System.out.println(simpleWSTest.deriveInfoFor(bob));
  } catch (Exception ex) {
   ex.printStackTrace();
  }
 }
 private static java.rmi.Remote createProxy() throws ServiceException {
  return 
  (java.rmi.Remote) (new EmployeeFactoryServiceLocator().getPort(EmployeeFactory.class));
 }
}
                

然而,当我们运行这个测试类时,我们没有得到所期望的输出。我们期望看到 Fred 是一个领薪水的雇员(Salaried Employee)而 Bob 是一个普通的雇员(Employee),取而代之,返回的信息指示 Fred 和 Bob 两人都是普通的雇员(Employee)。换句话说,扩展信息并没有传递给 Web 服务。如果启动 WebSphere Studio Application Developer (以下简称 Application Developer)中的 TCP/IP监视器,您将会看到薪水(Salary)信息从未传递给服务器。

当您观察所生成 WSDL的内部,您将很快发现为什么这个示例不起作用:上述扩展标签从未生成。原因很简单,为值类型构建WSDL 和相关的 Serializer 和 Deserializer对象的代码生成器并不知道它们需要特别处理子类,因为只有超类是直接在SEI 中引用的。

Java2WSDL 作为事实上完成在 ApplicationDeveloper 中“幕后”生成 WSDL 的工作的 WebSphere Application Server5.02 工具,实际上能够处理这种情况。您需要显式地给 Java2WSDL提供扩展,然后  Java2WSDL 把它们映射到 WSDL 文件。您可以使用 -extraClasses 选项来做到这一点。例如:

Java2WSDL EmployeeFactoryIF -extraClasses SalariedEmployee
                

然而,Java2WSDL 的这种特征还没有在 ApplicationDeveloper 工具中提供。因此,要在 Application Developer 5.1 中引入参数继承,您需要在 Web 服务中添加包含基类的每个子类的方法,以便使Java2WSDL 工具把适当的 Schema 扩展放到 WSDL 文件中去。在我们的示例中,我们可以通过把一个具有如下形式的方法添加到 SEI 及 JavaBean Web 服务实现类中来做到这一点:

public void dummy(SalariedEmployee e)
                

在添加了这个方法并重新运行 Web 服务向导后,会生成正确的 WSDL。然后您就可以在键入的以基类作为方法参数的方法中使用基类或任何一个子类。下面的 WSDL 类型声明展示在添加了“dummy”方法后我们的示例中的正确 WSDL 应该看起来是什么样的。

wsdl:types>
  <schema elementFormDefault="qualified" 
  targetNamespace="http://lukas.ws.demo.ibm.com"   xmlns="http://www.w3.org/2001/XMLSchema">
   <complexType name="Employee">
    <sequence>
     <element name="name" nillable="true" type="xsd:string"/>
    </sequence>
   </complexType>
   <element name="deriveInfoFor">
    <complexType>
     <sequence>
      <element name="e" nillable="true" type="impl:Employee"/>
     </sequence> 
    </complexType>
   </element>
   <element name="deriveInfoForResponse">
    <complexType>
     <sequence>
      <element name="deriveInfoForReturn" nillable="true" type="xsd:string"/>
     </sequence>
    </complexType>
   </element>
   <complexType name="SalariedEmployee">
    <complexContent>
     <extension base="impl:Employee">
      <sequence>
       <element name="salary" type="xsd:int"/>
      </sequence>
     </extension>
    </complexContent>
   </complexType>
   <element name="dummy">
    <complexType>
     <sequence>
      <element name="e" nillable="true" type="impl:SalariedEmployee"/>
     </sequence>
    </complexType>
   </element>
  </schema>
 </wsdl:types>
                

此时,重新运行前面描述的测试类会产生正确的结果:

----------Salaried Test---------
Name=Fred Person is a SalariedEmployee 1000
----------Regular Test----------
Name=Bob Person is a Regular Employee
                





回页首


在 .NET 上测试互操作性的结果


在测试完值类型继承在 WebSphere Application Server V5.02 和 WebSphere Studio Application Developer Version 5.1 的 JAX-RPC 实现中有效之后,我们需要确定生成的 WSDL 是否将与 .NET 兼容。来自 WebSphere 执行组的 Andrew Spyker 采用如此生成的全部 WSDL 并且生成在 Microsoft Visual Studio 中用于 C# 的客户端代理。相关的代码片段如下所示:


public class Employee {
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
        public string name;
    }
    /// <remarks/>
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://lukas.ws.demo.ibm.com")]
    public class SalariedEmployee : Employee {
        /// <remarks/>
        public int salary;
    }
                

在本例中,Visual Studio .NET 采用与 JAX-RPC 同样的方式来解释 WSDL:即作为值对象继承。在编写完一个大致等同于上面用 Java 展示的简单的 C# .NET 客户端程序后,我们观察到 .NET 客户端生成了适当的 SOAP 消息来表示对服务的请求:


<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<deriveInfoFor xmlns="http://lukas.ws.demo.ibm.com">
<e xsi:type="SalariedEmployee">
<name>Hello World</name>
<salary>10</salary>
</e>
</deriveInfoFor>
</soap:Body>
</soap:Envelope>
                

因而,在这个简单的示例中,我们发现 C# 客户端将以预期的方式与 Java 服务器进行互操作(假定在生成的 WSDL 中给出相关的信息)。





回页首


推荐:SOA 的设计

尽管我们用 Microsoft Visual Studio和 .NET 框架进行的互操作性实验产生正确的结果,但是我仍旧担心这种方法将不会普遍可移植。之所以有这样的担心,主要是因为值对象继承的扩展机制的使用在 WS-I 基本概要(Basic Profile)的范围之外(即便并未明确地加以排除)。目前,在 WS-I 基本概要中没有提及使用扩展结构,又加之 WS-I 一致性测试包并未包括这种情况。因此,可以合理地推断这方面的互操作性作为 WS-I 一致性的一部分并未经过正式测试。

WS-I Basic Profile 的目标是确定在互操作性场景上引起问题最多的 Web 服务领域,并且通过坚定地判定每个有争议的方面来限制将来出现这样的问题。然而,这是一个学习的过程,即使 WS-I 基本概要现在处于经过最终批准的的 1.0 状态,但是互操作性问题还是不断地被发现。

而另外一件需要考虑的事情是,我们正在处理值类型(或使用现在普遍接受的术语:“数据转移对象(Data Transfer Objects)”)。下面引用的是 MartinFowler 对这些对象的精辟描述:

“在许多方面,数据转移对象都是一种我们的父辈曾经告诫我们从来就不要编写对象。数据转移对象常常只不过是一些字段以及这些字段的 getters 和 setters。这种通常令人讨厌的东西德价值在于,它使得您可以在网络上移动某个单元中的信息的几个部分--对于分布式系统,这是非常重要的诀窍。"
(摘自 Patterns of Enterprise Application Architecture,第 427 页)

在这里需要认识到的关键事情是,这些“令人讨厌的东西”存在的一个也是惟一的理由是:把数据从一个地方移动到另一个地方。对象继承最好应用于行为而非数据(请参见 [Auer]对这个问题透彻的论证)。继承行为的能力是最常用的设计模式的核心。然而,如果给定数据转移对象的具体种类,通常就没有行为用于它们的继承。同样地,继承的实用性微乎其微。





回页首


结束语


即使我们能够在使用值类型的继承方法展示互操作性的措施,但是我还是建议小心地使用这个方法。如果 WS-I 规范不涉及这个问题,那么它就需要在 WebSphere 中进行额外的工作来使这个方法有效,而且使用这样的一个方法所获得的好处是极少的,而出现互操作性问题的风险有可能超过获得的潜在利益。



参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • [Auer] Ken Auer 撰写的“Reusability Through Self-Encapsulation”,最初发表在 Pattern Languages of Program Design 中,Addison-Wesley,Reading,MA,1994 年,可以在: http://www.rolemodelsoftware.com/moreAboutUs/publications/articles/self-enc.php 中找到。



  • [Fowler] Martin Fowler 撰写的 Patterns of Enterprise Application Architecture,Addison-Wesley,Reading,MA,2002 年



  • [WSDL12] W3C 制订的 Web 服务描述语言(Web Services Definition Language)规范(版本 1.2),可以在 http://www.w3.org/TR/wsdl12/ 中找到。




关于作者

Kyle Brown 是 IBM WebSphere 软件服务部(Software Services for WebSphere)的高级技术组成员。Kyle 向财富 500 强客户提供关于面向对象主题和 J2EE 技术的咨询服务、培训和指导。他与别人合著了 Enterprise Java Programming with IBM WebSphereWebSphere AEs 4.0 Workbook for Enterprise Java Beans(第 3 版)The Design Patterns Smalltalk Companion 。他还经常在研讨会上发表关于企业 Java、OO 设计和设计模式的演讲。




对本文的评价

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

建议?




回页首


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