使用 Apache Axis 和 Castor 创建 Web 服务

级别: 中级

Kevin Gibbs, 软件工程师, IBM
Brian D Goodman, IT架构师,
Elias Torres, 软件工程师, IBM

2003 年 10 月

    最近的研究已经指出了通过 RPC 使用文档方式(Document-style)Web 服务的好处--它们对于XML更清晰、更自然,并且使对象交换更简单。然而,使用 Axis 部署文档方式服务可能就不那么容易了,因为 Axis 的数据绑定框架使用起来比较困难,并且不支持 XML-Schema 的某些流行功能,而更重要的是,缺少验证支持。本文论述了这些不利的方面,还通过提供循序渐进的教程,说明了如何集成 Axis 与 Castor 数据绑定框架,来组合 Axis 的 Web 服务技术与 Castor 的数据绑定功能,以创建一个两全其美的 Web 服务。

三年以前,当 Web 服务进入技术社团的视野时,远程过程调用(RPC)接口是通用的实现。许多人了解了 SOAP,并且开始走上了熟悉的技术道路,比如用于公开服务器端数据和功能的 RMI、CORBA 和他们自己的自定义修改——这些复杂的、封闭的系统定义了如何以严格但仍然是高度编码的方式来发送和接收请求。编码 Web 服务的 RPC 方式通过提供自动方法来公开和调用方法而迅速地变得流行起来。但是,RPC 方式编码终究只是有限而不自然地使用了它的基础技术 XML。它相当于技术的错用————而在文档方式服务中,简单的 XML 独自提供了所需的全部表示。以最自然、最简单的方式保存技术标准是 Web 服务的真谛,文档方式就是这样的,在文档方式中,公开了接口,隐藏了后端和中间件系统,并且存在大量的动态发现、绑定和无尽的重用。

本文将展示如何利用 Castor XML 绑定使 Apache Axis 环境中的文档方式 Web 服务更简单、更清晰、更直观。本文首先讨论了 Web 服务编码方法,并且解释了为什么 Castor 和 Axis 共同构成了一个好的解决方案。本文为创建和运行文档方式 Web 服务的所有步骤——从设计 schema 和服务到生成服务和客户端代码——提供了说明和解释。本文讲解了如何配置 Axis 来使用 Castor,并且介绍了开发人员在解决棘手的问题时可能遇到的“局限性(gotcha)”。

文档方式的正反两面
开发人员在开发 Web 服务时遇到的一项挑战是存在两种调用模型:RPC 和文档方式。有几篇好文章详细地论述了这两种模型之间的区别,但是下面的部分将简要地介绍这些不同之处,以给本文的其余部分作一个铺垫。
 

RPC 方式编码之所以看起来有吸引力,是因为从概念上讲,它与开发人员已经使用了多年的其他实现架构(如 CORBA 或 RMI)是相同的。文档方式引起了人们的兴趣,但是 RPC 比较容易并且显示出技术相当简单,正因如此,文档方式经常受到冷遇。然而,过去的经验证明,快速开发是一项技术的关键,需要立即通过网络发送真实复杂的对象。为了进行显示,发送字符串、整数抑或数组就可以了,但是现实世界使用复杂的数据结构和模型来编码数据。为了处理这种情况,SOAP RPC 实现支持复杂对象序列化和反序列化。只要对象遵循 Java Bean 规范,就可以将对象转换成 XML 文档,并且采用对开发人员透明的方式进行处理。这是非常有吸引力的——用几行简单的代码就可以通过网络发送真实业务数据对象,而无需考虑基础实现。

RPC 的优点

    * 在一般的情况下,简单的请求/响应
    * 更易于使用,比如代表开发人员执行从XML到方法签名的映射

文档方式的优点

    * 更丰富、更完整的数据描述
    * 更大的灵活性
    * 更好地适合工作流和异步处理
    * 通常更快速
    * 与现有的XML表示的互操作

共同的制止因素

    * 解析器开发(在 DOM 或 SAX 中) 
    * 从接收的文档到Java值对象的自定义、冗长的内部映射

但是反过来说,通过 RPC 使用复杂对象也有缺点。这种方法常常产生集成问题。一种实现的序列化可能与另一种实现的反序列化不相匹配,因为用于 XML SOAP 编码过程的 Java Bean 的定义不明确。开放技术突然暴露出一个大的缺陷——Apache SOAP 在处理 .NET 时遇到麻烦,原因在于它们的实现之间的差异,因而迫切需要更加开放的服务。

文档方式很好地融合了定义明确的结构和互操作性。这是通过用于定义复杂对象的标准 XML-Schema 实现的。与 SOAP 编码比较起来,XML-Schema 是一种严格而容易理解的标准,可以用于定义结构。XML-Schema 在定义复杂结构方面具有很大的灵活性,并且同时确保了Web服务的所有承诺——语言、平台、环境和传输中立(在使用通用应用程序编程接口的情况下)。

文档方式获得了 XML-Schema 的所有优点,看来似乎能够解决所有的 Web 服务难题。然而,开发人员在选择文档方式时必须进行权衡。文档方式的一个不利的方面是增加了复杂性。开发人员会意外地发现,需要进行艰难的工作来解析 XML 文档和执行必要的转换,以载入其他数据 bean 和带有输入数据的方法请求。这对于服务器和客户端来说都是适用的。它意味着编写自定义 SAX 处理器,而使用和维护 SAX 处理器都特别困难。

正在发展的解决方案:Apache Axis

Apache Axis是最流行的Web服务工具包之一。Axis支持RPC和文档方式服务,因此,它看来似乎是文档方式服务的真正开端。在使用文档方式服务时,您仍然需要以某种方式处理输入的XML数据。Axis提供了一个方便的工具来帮助完成此项艰苦的工作,这个工具名为WSDL2Java。WSDL2Java不仅可以为您的方法生成客户机和服务器的代码存根,而且可以生成实际bean,来对用XML-Schema定义的模型中的数据进行建模。然后, WSDL2Java将从XML中自动载入这些bean。通常这个过程称为数据绑定,它是XML-Schema背后的运动的支柱。WSDL2Java显得有一些特异,但是往往它帮助事情的进展。很明显,这样的一个工具是非常有用的,但不幸的是,它并不是客户端存根生成的终结技术。它还面临着一些实际的问题:

    * WSDL2Java 陷入了大多数技术所遭遇的陷阱,也就是 利用 schema 支持迎头赶上(playing catch-up with schema suppor)。编写一个工具,让它可以正确而完整地处理这些非常复杂的XML-Schema标准并不是一件小事情。就其本身而言,这代表了和 Axis 同样困难的工作。WSDL2Java在这方面显得不够,有缺陷,并且没有对许多 XML-Schem 特征提供完全的支持。例如对属性组和选项组的支持。但是这些地方正在改变,因为对WSDL2Java所做的工作也在继续。然而,编写和维护如此复杂的代码并不是 Axis 所看重的,并且,WSDL2Java将继续推进迎头赶上的策略,以满足独立数据绑定解决方案的功能。
    * 另外一个问题和第一个问题有关。WSDL2Java 生成的代码 缺少 XML 验证(lacks XML validation) 能力。当您开始使用XML文档的时候,验证是非常重要的,而且 XML-Schema 允许验证过程自动发生。然而,WSDL2Java 还没有一个机制来完成这一工作。
    * 字符串的序列化和反序列化接口 不是直观的(not intuitive)。如果您的 XML 对象使用的是 WSDL2Java 的数据绑定代码,选择就是,您将需要在某个时候将对象编码或者解码为XML字符串。虽然这对 WSDL2Java 的生成对象来说是可行的,但却并不是一件容易的事情。需要安装一个庞大的框架,并且有可能出现许多头疼的事情,虽然它看起来好象是一项简单的任务。

开发社区对使用Apache Axis有一些复杂的反映和困惑,还有另外一个原因:开发社区是从 Apache Axis 的 beta 测试阶段开始伴随它一起壮大的,经历了 Apache Axis 发展的整个过程。需要了解的莫名其妙的工作区和配置机制随着版本的更新而不断变化。这就是开发代码主体的现实。最初,当 Axis一开始出现,重点关注的是 RPC Web 服务。因为这个原因,对RPC服务的支持是最多的,并且它的接口对于开发人员来说更稳定同时也更有名。直到现在,文档方式的文档、示例、Axis的样本配置都是有限的。随着多种使用文档方式的内部配置选项(如包、文档或者消息)的出现,开发人员在建立它们自己的文档方式的服务时要经历更多的决策和复杂性。然而,这些配置方面的困难并未形成大的障碍,无论如何,使用 RPC 仍然是调用 Web 服务的一种快速而便利的方法。当然,Axis还在不断的发展,这个困难将自动解决。不过,下面我们将一步一步地讲解如何使用文档方式的服务,您将会发现这并不很难。

Axis 和 Castor:皆为最优

Castor 是一个独立的 XML 数据绑定包,提供了从 XML-Schema 到 Java 对象的映射。这些 Java 对象看起来很像bean,但是可以编组(marshal)或者解组(un-marshal)为XML字符或者流,更重要的是可以对原始的Shcema进行验证。Castor是一个开放源码的工作,遵循了开放的Web服务技术。这得到了一个非常活跃的开发组织的支持,因此,产生了大量的信息和 Web 内容,使得开发人员可以更有效的利用这些技术。

我们在 Apache Axis 和 WSDL 不足的地方用 Castor 来构建所有级别的最优解决方案。我们将获得诸多好处,直观的数据绑定接口,更完全支持的 schema 实现,同时还利用了 Axis 框架的所有 Web 服务能力。 图1显示了 Axis 和 Castor 之间的关系。


在我们的解决方案中 Axis 和 Castor 之间的关系图

必备知识
本文的读者是熟悉 Web 服务和相关的创建和部署技术的媒介 Java 开发人员。如果您可以编写基本的 WSDL 并使用 Axis 来部署一个服务,那么您就具备了阅读本文的预备知识。同样,本文假定读者掌握 XML 和 Schema 的知识。所有的代码都是使用 WebSphere Studio Application Developer 和 WebSphere Application Server versions 4.0.4、4.0.6 来开发、测试和部署的。也可以很轻松地用其他的开发和部署环境、库来进行替换,并且能获得同样的效果,但是,在本文中我们集中于这些环境。
 

获得最新的 Axis 和 Castor

要构建和运行这个项目,您需要有 Apache Axis 和 Exolab 的 Castor。从 http://www.castor.org 可以很容易地得到 Castor 以及下载和安装指南。要获得 Apache Axis 相对来说难度更大一些,但也不难到哪儿去。在写作本文时,使 Axis 和 Castor 可以互操作的代码还不在发布的 beta 版本中,只能在CVS中得到。到读者阅读本文时,这一功能将很可能已经在最新发布的JAR中了。然而,在此之前,要获得最新版本的 Axis 以及这个项目所需的 org.apache.axis.ser.CastorSerializer 和 Deserializer 代码,最好的方法是从 CVS 获取,或者下载一个最新测试版(nightly build),这两种方式在 http://ws.apache.org/axis/cvs.html上都有。按照这里介绍的方法来下载和构建一个Apache Axis的副本,比想象中的会更容易一些,这要感谢Apache Ant的构建架构。

在 WebSphere 中启用 Axis

这个项目的样本环境是 WebSphere Application Server V4.0.4。在 WebSphere 或者其他 Java 容器安装 Axis 不会太难,但是正确地执行这些步骤仍然很重要。关于如何进行操作的指南在 Apache Axis 文档中(请参阅 参考信息),您也可以在本地 CVS 检验版(checkout)或最新测试版(nightly)中的 /xml-axis/java/docs/install.html 中找到。

如果您使用 WebSphere Application Studio V5.0,您可能会遇到一个“局限性”,这个局限性是关于 WebSphere Application Developer V5 上安装的环境和您试图与 Apache Axis 一起使用的环境之间的 JAR 冲突的。如果您遇到了这些问题,可以尝试这个快速修复(quick fix):在测试服务器的服务器配置中,将环境选项面板(Environment Options)系统属性(-D property)的“com.ibm.ws.classloader.warDelegationMode”设置为 false 。操作步骤如下: Server perspective > the server you are using for our example > Environment Options > System Properties > Add....

定义 Schema 和服务
要开发Web服务,您需要有一个 Schema,它表示两个部分,一个部分是您将要使用的数据,另一个部分是描述您将要公开的方法的的服务描述。讲解如何编写一个 XML-Schema 或者 WSDL 不在本文的讨论范围之内,不过在别的地方可以找到关于如何完成这一工作的文档。
 

为了达到本文的目标,我们将创建一个示例服务: StockQuote服务。

为您的数据定义 Schema:StockQuote.xsd

您的服务所需要的第一件事情就是用 XML-Schema 描述您的数据模型。在 清单1中展示了将用在 StockQuote 服务中的简单数据模型,它应该是自解释的。
清单1. StockQuote服务的数据模型StockQuote.xsd


<xsd:element name="quote">
     <xsd:complexType>
          <xsd:sequence>
               <xsd:element name="symbol" type="xsd:string"/>
               <xsd:element name="volume" type="xsd:integer"/>
               <xsd:element name="lastTrade" type="lastTradeType"/>
               <xsd:element name="change" type="changeType"/>
          </xsd:sequence>
     </xsd:complexType>
</xsd:element>

<xsd:complexType name="changeType">
     <xsd:sequence>
          <xsd:element name="dollar" type="xsd:float"/>
          <xsd:element name="percent" type="xsd:float"/>
          <xsd:element name="positive" type="xsd:boolean"/>
     </xsd:sequence>
</xsd:complexType>

<xsd:complexType name="lastTradeType">
     <xsd:sequence>
          <xsd:element name="price" type="xsd:float"/>
          <xsd:element name="date" type="xsd:long"/>
     </xsd:sequence>
</xsd:complexType>

除了定义元素和 complexTypes 来代表您的数据模型之外,您还必须定义接口的输入/输出。这些就是Web服务将公开的 方法,并且将在 WSDL(下一步讲解如何创建 WSDL)中进行指出,如 清单2所示:
清单2. StockQuote.xsd的StockQuote服务的方法签名


<xsd:element name="getStockQuote">
<xsd:complexType>
  <xsd:sequence>
   <xsd:element name="symbol" type="xsd:string" />
  </xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="getStockQuoteResponse">
<xsd:complexType>
  <xsd:sequence>
   <xsd:element ref="quote" />
  </xsd:sequence>
</xsd:complexType>
</xsd:element>

为您的服务定义 WSDL:StockQuote WSDL

接下来,您必须为您的 Web 服务创建一个 WSDL 文件。您可以采用标准的方式来完成这一工作,但是这里有几个地方需要重点关注。首先,注意 清单3中用黑体突出显示的部分。在标准方式中,您是用与您的数据模型相关联的 URI 来定义“types”命名空间前缀。其次,您将所需的独立 Schema 文件(StockQuote.xsd)导入 WSDL。最后,您在 WSDL 的消息声明中使用了 “types” 命名空间。为什么做这些工作显得很重要?因为 Castor 天生就不支持在文件中嵌入 <xsd:schema> 节点,在本例中即为 WSDL 的 <types> 部分中的 <xsd:schema>  节点。我们发现,像这样使用外部文件来定义您的数据模型和方法,不仅清晰、易于维护,而且更重要的是,它容许 Castor 和其他工具直接与您的文件形式的 Schema 进行交互。
清单3. StockQuote 服务的 WSDL 文件


<definitions
  targetNamespace="http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl"
  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:tns="http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 
        xmlns:types="http://w3.ibm.com/schemas/services/2002/11/15/stockquote">
   <types>
     <xsd:schema elementFormDefault="qualified"
        targetNamespace=
        "http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl/importtypes">
         
        <import namespace=
                "http://w3.ibm.com/schemas/services/2002/11/15/stockquote"
             location="StockQuote.xsd" />
     </xsd:schema>
   </types>
   <message name="getStockQuoteReq">
     <part name="parameters" element=
        "types:getStockQuote" />
   </message>
   <message name="getStockQuoteResp">
     <part name="parameters" element=
        "types:getStockQuoteResponse" />
   </message>
   <portType name="StockQuotePortType">
        <operation name="getStockQuote">
             <input message="tns:getStockQuoteReq" />
             <output message="tns:getStockQuoteResp" />
        </operation>
   </portType>
      . . .

     

这里惟一要提请注意的事情就是将 WSDL 设置为使用文档方式编码。注意 清单4中用粗体突出显示的部分,它是 WSDL 的继续。这些部分将告诉 WSDL 以及 WSDL2Java,您使用的是文档方式的Web服务,并且您将生成文档方式(非 RPC)的服务请求。
清单4. StockQuote 服务的 WSDL 文件(续)


      <binding name="StockQuoteSOAPBinding" type="tns:StockQuotePortType">
           <soap:binding style=
        "document"
              transport="http://schemas.xmlsoap.org/soap/http" />
           <operation name="getStockQuote">
                <soap:operation style=
        "document" soapAction="getStockQuote" />
                <input>
                     <soap:body use=
        "literal" />
                </input>
                <output>
                     <soap:body use=
        "literal" />
                </output>
           </operation>
      </binding>
      <service name="StockQuoteService">
           <port name="StockQuoteSOAPPort" binding="tns:StockQuoteSOAPBinding">
                <soap:address
                   location=
                   "http://localhost:9081/AxisWeb/services/StockQuoteSOAPPort" />
           </port>
      </service>
</definitions>

     

生成所需的代码和存根
既然您已经准备好了您的服务定义和 Schema,这样就可以着手于 Axis 和 Castor。Axis 和 Castor 都需要生成一些代码,并且您可以把 Castor 生成的代码放在 Axis 生成的代码之上,这样就就可以做到“两全其美”了,既采用了 Axis 的 Web 服务客户端和服务器代码,有带有 Castor 的数据绑定。这第一个部分,将显示如何生成这些代码,下一个步骤将演示如何重新配置生成的映射,这样可以获得所期望的互操作性。
 

使用 WSDL2Java 构建客户端和服务器端存根

WSDL2Java 生成 Java 类(用于数据模型中的对象的数据绑定)、客户端和服务器存根代码(用于连接您的方法)、以及服务绑定信息(用于 Axis 服务器)。我们将继续使用后面的两个部分:存根和服务绑定,而用 Castor 生成的代码来替代数据模型代码。但是要完成这一工作,首先必须运行 WSDL2Java 来生成我们将需要的存根和服务绑定。

要使用 WSDL2Java 生成所需要的文件,可以运行以下命令:

%java org.apache.axis.wsdl.WSDL2Java -s "Web Content/StockQuoteService.wsdl"
--NStoPkg http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl=com.ibm.w3.services.stockquote
--NStoPkg http://w3.ibm.com/schemas/services/2002/11/15/stockquote=com.ibm.w3.services.stockquote
-o "Java Source"

上面的命令将生成如 清单5所示的文件。
清单5. WSDL2Java生成的文件


StockQuoteSOAPBindingImpl.java // a server-side service implementation template class,

StockQuotePortType.java        // a server portType interface,
StockQuoteSOAPBindingStub.java // a server stub class,
                                   where we'll write the getStockQuote method,
StockQuoteService.java         // a client-side service interface,
StockQuoteServiceLocator.java  // a client-side service implementation (the locator),

deploy.wsdd                    // the service bindings for the Axis server,
undeploy.wsdd

_quote.java                    // plus the classes to represent the data model,
ChangeType.java                // which we will be replacing with the ones generated
LastTradeType.java             // by Castor.
. . .

当您在 WSDL2Java 中使用命令来生成文件时,您提供了几个参数。 NStoPkg 参数指定了 Java 包,用于您的 StockQuote.xsd 文件的不同的命名空间,这将被 WSDL2Java 自动从 WSDL 文件加入进来。下一步,当您运行 Castor 来生成数据模型类,您可以使用同样的映射。对于 WSDL2Java 还有一些可选的额外参数,在本文中没有讲述,那将不使用 wrapped 的方式。这一个选项可以通过添加“-W”命令行来指定。在 WSDL2Java 中, wrapped 形式意味着什么呢?它控制了 WSDL2Java 是如何生成方法客户端和存根。对于一个文档形式的服务,通过 -W 选项来指定不使用 wrapped 方式,这将映射到一个方法,类似于下面用 StockQuoteSOAPBindingStub 类生成的:

public GetStockQuoteResponse getStockQuote(GetStockQuote gsq)

换句话说,在 StockQuote.xsd 文件中定义的整个 <GetStockQuote> 元素,将作为一个单独的 bean 传递给您的方法,在 bean 中有三个字段,如 清单2所示。另一方面,对于一个标准的 wrapped 方式的服务(也就是在这个示例中生成的),它将映射到如下的一个方法:

public Quote getStockQuote(String symbol)

本文中将使用 wrapped 形式,这种形式让我们觉得更易于阅读,这样也可以免于书写额外的代码。

使用 Castor 生成数据绑定

目前,我们可以继续我们的工作,使用 Castor 生成数据模型绑定,来替换 WSDL2Java 所创建的。因为 Castor不是特定于 Web 服务的,它是直接使用 XSD 文件,而不是 WSDL 文件。因此,可以使用下面的命令,将 XSD 文件传递给它:

%java org.exolab.castor.builder.SourceGenerator -i "Web Content/StockQuote.xsd"
-package com.ibm.w3.services.stockquote
-nomarshall
-dest "Java Source"
-f

您使用命令行,并传递参数给 Castor。参数 -package 是对应于 WSDL2Java 的 NStoPkg 参数。参数 -nomarshall 告诉 Castor 不要在所生成的 bean 中产生编组(marshall)方法(marshall、unmarshall、validate)。在您的示例中,这些方法是不必要的,因为 Castor Serializer and Deserializer for Axis 是直接使用 Castor Marshaller 和 Unmarshaller 类,这可以实现同样的目的,所以,我们使用参数来关闭它。如果您计划使用这些对象作为一个大系统的一部分,也可以打开这个参数。参数 -f 仅仅告诉 Castor,在没有提示的前提下去覆盖所有现有的文件。如果您注意到了这一点,您将清楚,这将覆盖那些在前面步骤中由 WSDL2Java 所生成的一些数据模型 bean。但是不用担心,因为您不用这些文件,所以这将不是一个问题。

Castor 其余的选项在 castorbuilder.properties 文件中指定。大部分属性都不重要,但是,有一个很重要的设置: javaclassmapping 设置。

# Java class mapping of <xsd:element>'s and <xsd:complexType>'s
#
org.exolab.castor.builder.javaclassmapping=element

这个设置决定了 Castor 如何从您的 Schema 中生成类。根据您如何编写您的 Schema,您也许想使用 element 或者 type 。两者的区别是 element 方法为所有的 complexType 类型的元素生成类。对于所有的顶级 <complexTypes> 会生成抽象类。顶级 <complexType> 类型的 Schema 中的元素都将为之生成一个类,这个类继承了 <complexType's> 抽象类。对于 <simpleType> 类型的元素将不生成类。然而,可以直接使用基本Java类型。另外一个选项, type 方法的行为是不一样的。并不为没一个元素生成类,来扩展为 <complexTypes> 创建的类, type 选项将为所有的顶级 <complexTypes> 类型和所有的内联(inlined) anonymous <complexTypes> 创建类。元素将格式化为这些类的实例,而没有它们本身的类。

这两个选项解释起来有一些复杂,但是,根据您的 Schema 是如何定义的,一个选项将是每一个 Schema 的基本选项。因此,对您自己的 Schema,要先试验一下两种方法,看您更倾向于那一个类。对于这个 StockQuote.xsd 示例,我们发现 element 方式工作得更好,所以,在这里我们使用这个方法来生成实例数据模型bean。

配置 Axis 和 Castor 来一起工作
现在我们可以来了解事情的本质了:让 Axis 去使用 Castor 生成的类,而不是使用它自己生成的类。这个工作是本文的最关键之处,在本文中描述了如何完成这个“艰巨”的工作,在客户端和服务器端代码存根中 Axis 可以使用 Castor 的类。但是不要担心这个难题——学习完本文的一步一步的详细指导,这个工作就不再困难了。要完成这一工作,需要修改 Axis 服务器端存根类、以及 Axis 生成的 .wsdd 文件。
 

修改服务器端存根去使用 Castor 类

要让存根使用 Castor 类,您只需修改存根文件中的类名就可以了。正如前面我们提到的一样,尽管通常 Castor 和 WSDL2Java 将生成同样的类名,导致了同样类名的存根,但是并不是所有情况都会如此。因此,检查 WSDL 客户端和服务器端存根代码,确保已经设置为返回和 Castor 生成对象同样的正确类名。

请再看 清单5,可以看到两个文件, StockQuotePortType.java 和 StockQuoteSOAPBindingStub.java 显示为用于服务器。为了实现目的,我们必须修改这两个文件。必要的惟一修改就是检查所有的类名,确保它们已经指向了 Castor 生成的类,而不是 WSDL2Java 生成的类。

在本实例中,只有一个类名发生了改变: _quote 。这个 WSDL2Java 生成的类 _quote ,在 Castor 中名称改成了 Quote 。它们仍然在同一个包中,正如我们生成这两个类的情形一样。但是我们必须在引用它们的地方修改类的名字。

然后,文件 StockQuotePortType.java ,的下面一行
public com.ibm.w3.services.stockquote. _quote getStockQuote(java.lang.String symbol) throws java.rmi.RemoteException;

改变成:
public com.ibm.w3.services.stockquote. Quote getStockQuote(java.lang.String symbol) throws java.rmi.RemoteException;

对于文件 StockQuoteSOAPBindingStub.java ,我们必须更改同一行,
public com.ibm.w3.services.stockquote. _quote getStockQuote(java.lang.String symbol) throws java.rmi.RemoteException {...

改变为
public com.ibm.w3.services.stockquote. Quote getStockQuote(java.lang.String symbol) throws java.rmi.RemoteException {...

现在,所有的服务器端代码引用都指向了 Castor 类。这意味着,我们就可以修改 deploy.wsdd 文件去使用这些类和 Castor 配置。

修改和部署 server-config.wsdd 文件

第二个任务是修改 deploy.wsdd 文件。下面先让我们概览一下这个文件,需要修改的部分已经着重显示,如 清单6。
清单6. 修改StockQuote服务的 deploy.wsdd 文件


<service name="StockQuoteSOAPPort" provider="java:RPC" style="wrapped" use="literal">
    <parameter name="wsdlTargetNamespace"
          value="http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl"/>
    . . .

    <typeMapping
      xmlns:ns="http://w3.ibm.com/schemas/services/2002/11/15/stockquote"
      qname="ns:changeType"
      type="java:com.ibm.w3.services.stockquote.
        ChangeType"
      serializer="org.apache.axis.encoding.ser.
        castor.CastorSerializerFactory"
      deserializer="org.apache.axis.encoding.ser.
        castor.CastorDeserializerFactory"
      encodingStyle=""
    />
    <typeMapping
      xmlns:ns="http://w3.ibm.com/schemas/services/2002/11/15/stockquote"
      qname="ns:lastTradeType"
      type="java:com.ibm.w3.services.stockquote.
        LastTradeType"
      serializer="org.apache.axis.encoding.ser.
        castor.CastorSerializerFactory"
      deserializer="org.apache.axis.encoding.ser.
        castor.CastorDeserializerFactory"
      encodingStyle=""
    />
    <typeMapping
      xmlns:ns="http://w3.ibm.com/schemas/services/2002/11/15/stockquote"
      qname="
        ns:quote"
      type="java:com.ibm.w3.services.stockquote.
        Quote"
      serializer="org.apache.axis.encoding.ser.
        castor.CastorSerializerFactory"
      deserializer="org.apache.axis.encoding.ser.
        castor.CastorDeserializerFactory"
      encodingStyle=""
    />
  </service>

     

对于这个文件我们要做两种类型的更改,以及还需要警惕的“局限性”。所有的更改都是针对 <typeMapping> 元素。第一个更改是修改 type 属性下面的类名,使用 Castor 生成的类名,而不是 WSDL2Java 所生成的类名,如果这两个名字有差别的情况下就需要进行这个更改。在我们的范例中,对于最开始的两个映射, ns:changeType 和 ns:lastTradeType ,和 Castor 中的类名是一样的,所以没有必要进行更改。但是,对于 ns:quote 类,类名需要进行更改,将 WSDL2Java 生成的 _quote 类名更改为 Castor 生成的 Quote 类名。

第二个进行的更改是:在 <typeMappings> 中将所有的 serializers 和 deserializers 分别的更改为 org.apache.axis.encoding.ser.castor.CastorSerializerFactory 和 org.apache.axis.encoding.ser.castor.CastorDeserializerFactory 。这就告诉 Axis 使用 Castor 专有的类去序列化和反序列化这些类型的引入的 XML 文件,而不是缺省的 org.apache.axis.encoding.ser.BeanSerializerFactory 和 org.apache.axis.encoding.ser.BeanDeserializerFactory 类。这些类包含在最新版本 Axis 1.1 CVS 修订中的类,也正是为什么我们需要在 “获得最新的 Axis 和 Castor”步骤中获取Axis的CVS版本。

这里有一个需要提请注意的“局限性”,我们将着重强调一个地方也就是我们需要注意的。这个“局限性”必须处理 Axis 和 WSDL2Java 阅读和编写 XML 的方式。在我们的测试中,我们发现 WSDL2Java 偶尔生成无效的 XML,原因是在输出中错误的防止了一些“<”和“>”。在最终的 <typeMapping> 元素 qname 属性中就是这样的一个地方。生成的 qname 属性的值是“ ns:>quote ”,很明显并不是有效的 XML ,因为“>”是受保护的字符。在这种情况下,删除这个多余的“>”就能解决所有的问题,并且要盯住类似于上面情况的多余字符。

使用deploy.wsdd文件来部署服务
既然您已经更改了您的 deploy.wsdd 文件,而且做好了充分的准备,这时需要把它部署到服务器上。您可以通过从deploy.wsdd 文件中复制 <service> 元素到 WEB-INF/server-config.wsdd 文件,就可以完成这个工作。但是,您也可以使用 Axis 的便利部署工具来为您自己完成这一个工作。这个方法也确保了 server-config.wsdd 文件已经存在,如果不存在的话就创建这个文件,然后进行错误检查,确保这一方法的正确性。

为了运行这个部署工具,确保您的工作目录已经设置为 Axis 的根目录,在该实例中是 WEB-INF ,然后运行下面的命令:
%java org.apache.axis.utils.Admin server classes/com/ibm/w3/services/stockquote/deploy.wsdd

如果这个命令得到了正确运行,将不返回任何错误,并且您的服务已经得到了部署。现在,简单的启动您的服务器,然后可以进行下一步工作。

同样,如果您想在后面的某一个步骤卸载您的测试服务,运行下列命令:
%java org.apache.axis.utils.Admin server classes/com/ibm/w3/services/stockquote/undeploy.wsdd

在 Axis 上测试您的服务

现在您已经构建了所有的相关代码,集成 Axis 和 Castor,然后在 .wsdd 文件中部署服务。这时候,就可以进行测试了。通过指向您在 .wsdd 文件中定义的端点,您可以测试您安装的应用,在本实例中,是指向 <context root>/services/StockQuoteSOAPPort 。您可以看到结果如 图2所示。

 
Axis 运行我们的 StockQuote 服务的结果

如果您不能得到这个显示信息,这说明在 Axis 中安装 StockQuote 服务出现了错误,这时重新查看一下您的配置以及前面的步骤,看到底是什么地方出现了问题。

如果显示了这个消息,这时候您就可以编写一些代码来让 getStockQuote 方法做一些实际的工作。

编写方法
现在,什么都准备好了,但是您的服务器仍然不能做任何事情——您还没有实现 getStockQuote 方法。由于相应的事情都已经做好,要进行着一个工作就非常简单了。您只需要在 StockQuoteSOAPBindingImpl 中填写您的方法,该方法和在 清单2中的方法一一对应。
 

编写 getStockQuote 方法

因为这是一个简单的 Web 服务,在这一步骤中我们不用做太多的工作。但是该样本代码说明了使用 Castor 生成的数据绑定代码以及由 Axis 来填充是非常容易的。下面是我们的 getStockQuote 方法的样本代码,见 清单7。
清单7. StockQuoteSOAPBindingImpl.java 中的样本 getStockQuote 方法


public class StockQuoteSOAPBindingImpl implements
            com.ibm.w3.services.stockquote.StockQuotePortType {

    public com.ibm.w3.services.stockquote.Quote getStockQuote(java.lang.String symbol)
              throws java.rmi.RemoteException {
        Quote quote = new Quote();
        quote.setVolume(979012312);
        quote.setSymbol(symbol);

        Change change = new Change();
        change.setDollar(678F);
        change.setPercent(300F);
        change.setPositive(true);
        quote.setChange(change);

        LastTrade lastTrade = new LastTrade();
        lastTrade.setDate(new java.util.Date().getTime());
        lastTrade.setPrice(678F);
        quote.setLastTrade(lastTrade);

        return quote;
    }
}

这里需要着重注意的是:在这里使用的对象和在客户端使用的对象是一样的,并且已经由 Castor 根据它的 Schema 进行了验证,所以这些导入的数据可以安全的使用。如果导入的数据没有得到验证,在调用您的方法之前 Castor 会抛出一个异常。同样,对于验证您返回的数据这一机制将带来一些益处,并且,如果您返回的数据没有得到验证,当 Axis 试图序列化输出的 bean 的时候, Castor 将抛出一个异常。

另外,本例中尽管我们的 bean 非常简单——只有 Quote、Change 和 LastTrade 对象—— Castor 处理这些复杂的 XML-Schema 数据模型非常完美,但是 WSDL2Java 将会遇到问题。

构建客户端
既然您已经成功的在服务器端完成了运行 Axis 和 Castor 的所有工作,现在就可以构建一个客户端了。
 

使用动态的 Axis Web 服务客户端以及我们的 Castor 知识,我们将完成如下所示的一个初步工作,如 清单8。
清单8. 构建 StockQuote 服务的客户端的初步工作


     Service service = new Service();
     Call call = (Call) service.createCall();
     call.setTargetEndpointAddress(new URL(ENDPOINT));

     GetStockQuote request = new GetStockQuote();
     request.setSymbol("IBM");

     StringWriter sw = new StringWriter();

     Marshaller mar = new Marshaller(sw);
     mar.marshal(request);

     String xml = sw.getBuffer().toString();
     Document doc = XMLUtils.newDocument(new ByteArrayInputStream(xml.getBytes()));
     SOAPBodyElement[] input = new SOAPBodyElement[1];
     input[0] = new SOAPBodyElement(doc.getDocumentElement());

     Vector elems = (Vector) call.invoke(input);

这样就可以了,但是并不是这么自动化。然而,对 Axis 生成的客户端存根使用一些巧妙的办法,我们可以做得更好,可以有一个客户端可以自动化的完成所有的安装过程。


To make 为了让存根使用 Castor 类,简单的修改存根文件中的类名,就象和服务器存根中一样——虽然,通常 Castor 和 WSDL2Java 生成同样的类名,导致存根也是有同样的类名。但并不是所有情况都这样,所以检查和更正显得很重要。

您需要修改的文件是 StockQuoteSOAPBindingStub.java 。在本实例中, WSDL2Java 生成了用于 Quote 元素的类,名为 _quote ,但是 Castor 生成的名字为 Quote ,所以您需要在所有的地方进行更改。在这里并不列出所有的引用了这个类的地方,以及每一行需要更改,这是一个重复性的事情,并且对于阅读没有什么益处,我们将把这个工作交给您来做,仅仅需要浏览该文件,将所有对类 com.ibm.w3.services.stockquote._quote 的引用更改为 com.ibm.w3.services.stockquote.Quote 。

更改客户端,使用 Castor serializers 代替 Axis serializers

目前,我们进行关键的更改:修改客户端去使用 Castor 专有的 serializer 以及 deserializer 类,来代替缺省情况下 WSDL 生成的 BeanSerializers 。在服务器端,我们可以修改配置文件, deploy.wsdd ,设置使用哪个类来序列化和反序列化。不幸的是,客户端并不使用这样的一个配置文件,所以我们必须在代码中修改。然而,这是一个非常简单的修改,并且可以很清楚地看出,它和在服务器端对 deploy.wsdd 的修改是一样的。

要 Castor serializer 和 deserializer 类,您需要做的第一件事情就是将它们添加到可能的序列器(Serializer)类列表中(在 StockQuoteSOAPBindingStub.java 文件的构造函数 public StockQuoteSOAPBindingStub(javax.xml.rpc.Service service) 中创建)。
清单 9. 更改 StockQuoteSOAPBindingStub.java 中的构造函数


public StockQuoteSOAPBindingStub(javax.xml.rpc.Service service)
                                             throws org.apache.axis.AxisFault {
    if (service == null) {
        super.service = new org.apache.axis.client.Service();
    } else {
        super.service = service;
    }
        java.lang.Class cls;
        javax.xml.namespace.QName qName;
       
        java.lang.Class castorsf =
        org.apache.axis.encoding.ser.castor.CastorSerializerFactory.class;
       java.lang.Class castordf =
        org.apache.axis.encoding.ser.castor.CastorDeserializerFactory.class;
        java.lang.Class beansf =
        org.apache.axis.encoding.ser.BeanSerializerFactory.class;
        java.lang.Class beandf =
        org.apache.axis.encoding.ser.BeanDeserializerFactory.class;
        java.lang.Class enumsf =
        org.apache.axis.encoding.ser.EnumSerializerFactory.class;
        java.lang.Class enumdf =
        org.apache.axis.encoding.ser.EnumDeserializerFactory.class;
        java.lang.Class arraysf =
        org.apache.axis.encoding.ser.ArraySerializerFactory.class;
        java.lang.Class arraydf =
        org.apache.axis.encoding.ser.ArrayDeserializerFactory.class;
        java.lang.Class simplesf =
        org.apache.axis.encoding.ser.SimpleSerializerFactory.class;
        java.lang.Class simpledf =
        org.apache.axis.encoding.ser.SimpleDeserializerFactory.class;

        . . .

     

清单9显示了用粗体显示的构造函数的修改。这个修改是自描述的:它添加了 CastorSerializerFactory.class 和 CastorDeserializerFactory.class 作为后面代码可能使用的选项,定义了用于输入和输出对象的序列器(serializer)和反序列器(deserializers)。

更新客户端的最后一个步骤是:在上面提到的构造函数中,更改所有 cachedSerFactories.add(beansf) 和 cachedDeserFactories.add(beandf) 出现的地方,用 Castor 的 BeanSerializer  代替 Axis 的 BeanSerializer 。这等同于我们对 deploy.wsdd 的更改,这里我们列出了 Castor 序列化和反序列化类,而不是 Axis 的 BeanSerializer。在用到您的对象时,将每一个 beansf 和 beandf 的地方更改为 castorsf 和 castordf 。在所有出现 beansf 和 beandf 的地方,您都需要将它们改为 castorsf 和 castordf ,在本例中,这两者都出现了。

例如,如 清单10所示的一个代码块的更改,其修改用粗体表示:
清单10. 在 StockQuoteSOAPBindingStub.java 中更改的代码块


    qName =
    new javax.xml.namespace.QName(
    "http://w3.ibm.com/schemas/services/2002/11/15/stockquote", "lastTradeType");
    cachedSerQNames.add(qName);
    cls = com.ibm.w3.services.stockquote.LastTradeType.class;
    cachedSerClasses.add(cls);
    cachedSerFactories.add(
        castorsf);
    cachedDeserFactories.add(
        castordf);

     

在构造函数中更改每一个保留的代码块,然后您完成了这个任务: Axis 生成的客户端目前使用 Castor 序列化和数据绑定。

试用生成的客户端

现在,您可以编写客户端代码,如 清单11。
清单11. 最终的 StockQuote 客户端


     StockQuoteService service = new StockQuoteServiceLocator();

     StockQuotePortType port = service.getStockQuoteSOAPPort();

     Quote quote = port.getStockQuote("IBM");

     System.out.println(quote.getVolume());

这样更安全,更加自动化,而且更容易使用。并且,现在您获得了一个 Web 服务,使用 Axis来进行通信,使用 Castor 进行验证和数据绑定,这是一个端到端的情形。

Axis 和 Castor:文档方式
正如您在本文中所看到的,整合文档方式的服务、Castor 和 Axis 并不是想象的那么可怕,仅仅是安装有点复杂。但是一旦您成功了,您的 Web 服务将具备文档方式的灵活性和清晰度,对 Axis 健壮的支持,以及 Castor 的验证和数据绑定优势。
 

一旦您掌握了本文所讲的全部内容,还有许多有趣的方面有待您去钻研。例如,使用 Castor JDO,您只需写几行代码就可以让您的服务器端 Castor 对象把自己编组到 SQL 数据库中。您也可以使用 Castor 的标准表达式和验证支持来清理 Web 服务数据,这样您的服务和客户端的数据中出现错误的可能性就减少了。要了解关于这些主题的更进一步的信息,请参阅关于整合 Castor 和 Axis 的后续文章。

参考资料

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

    * 从 Castor web site 可以得到 Castor 的最新副本。

    * 参阅 Apache Axis 文档,它在 WebSphere 或者其他 Java 容器提供的 a guide for getting Axis set up 中。

    * Axis CVS page 包含了关于从 CVS 或者最新测试版本的文件中下载和构建 Axis 的指导。

    * 关于如何安装和使用 Axis,请查看 Axis installation instructions 文档页。

    * “ Reap the benefits of Document-style Web services” ( developerWorks,2002年6月)提供了更多关于文档方式服务的核心知识。

    * “ Data binding with Castor” ( developerWorks, 2002年4月)更加深入地解释了 Castor 的数据绑定框架的益处。

    * 系列文章: 逐步了解 Web 服务标准 ( developerWorks, 2002年10月),解释了 SOAP 的复杂性。

    * 开发 Web 服务( developerWorks, 2001年11月),提供了一个关于如何为 Web 服务定义 WSDL 的介绍。

    * 使用 XML-Schema 定义元素的基础 ( developerWorks, 2000年 8月)将帮助您为您的服务数据定义 XML-Schema。

作者简介
Kevin Gibbs是一位软件工程师,在英国剑桥大学学习 IBM 的高级 Internet 技术,获得了 MA 学位。他早先从事 SashXB for Linux 脚本环境研究,目前正研究多种 Internet 技术,包括 Web 服务和 blogging 架构。您可以通过 kagibbs at us.ibm.com 与 Kevin 联系。


Brian Goodman 是一位 IT 架构师,专门从事 IBM Intranet 的咨询、交流和协作。您可以通过 bgoodman at us.ibm.com 与 Brian 联系。


Elias Torres 是一位软件工程师,在英国剑桥大学学习 IBM 的高级 Internet 技术,获得了 MA 学位。他从事 Sash 脚本环境研究,探索一些技术,例如 blogging 和它们在公司环境的实际益处。您可以通过 eliast at us.ibm.com 与Elias联系。

你可能感兴趣的:(apache,Web,应用服务器,xml,IBM)