了解 Web 服务规范: 第 2 部分:Web 服务描述语言 (WSDL)

时间:2022-11-13 04:17:44

开始之前

本教程旨在帮助您了解 Web 服务描述语言 (Web Services Description Language)。本教程适合希望通过使用 WSDL 公开自己的服务供他人使用的开发人员,同时也适合拥有希望访问的服务的 WSDL 而需要创建客户机的开发人员。

为了学习本教程,您应该具有 SOAP 的基本知识(可以通过阅读本系列教程的第 1 部分了解 SOAP)和 XML 的基本知识。WSDL 与编程语言无关,但本教程最后的示例使用了 Java 和 Apache Axis2 项目。不过,其中的概念也适用于任何编程语言和环境。与此类似,本教程侧重于通常实现的 WSDL 1.1,但即将推出的 WSDL 2.0 的概念也与此相同,本教程将对二者的基本差异进行说明。

关于本系列

本系列教程以假想的报社 Daily Moon 为例,为了提高在竞争激烈的环境中的工作效率,其员工将使用各种 Web 服务来创建工作流系统,我们将在此过程中讲解各个 Web 服务基本概念。

第 1 部分比较简单,说明了 Web 服务背后的基本概念,并演示了如何使用 SOAP(后续教程讨论的大部分内容的基础规范)来将 Classifieds Department 连接到内容管理系统。

在第 2 部分中,Classifieds Department 将开展进一步的工作:使用 Web 服务描述语言 (WSDL) 定义 Web 服务预期产生的消息,从而使团队更方便地创建服务以及连接到服务的客户机。

在第 3 部分中,团队希望准备一系列服务,并希望能方便地查找这些服务。与此对应,统一描述、发现和集成(Universal Description, Discovery and Integration,UDDI)提供了可用服务的可搜索注册中心,以便使自己的服务为其他人所注意。

第 4 部分和第 5 部分讨论 WS-Security 和 WS-Policy,将详细说明如何保证该报社的服务的安全,以及团队为了访问这些刚提供了安全保护的服务需要进行哪些更改。

第 6 部分重点讨论互操作性,因为必须从单个系统访问来自几个不同实现的服务。这一部分还将 WS-I 证书中涉及的要求和测试。

最后,第 7 部分演示如何使用业务流程执行语言(Business Process Execution Language,WS-BPEL)来从各个服务创建复杂应用程序。

接下来让我们更为详细地了解一下本教程中将讨论的内容。

回页首

关于本教程

本系列的第 1 部分了解 Web 服务规范:SOAP 介绍了虚构的 Daily Moon 报社。具体来说,您了解了 Classifieds Department 所进行的工作。在该教程中,Classifieds Department 构建了一个客户机来在表示内容管理系统的 Web 服务间发送 SOAP 消息。在本教程中,由于对所看到的结果印象深刻,Classifieds Department 决定构建自己的 Web 服务来接收和管理 classifieds 数据库中的广告。不过,为了允许其他人使用此服务,他们将需要创建 Web 服务描述语言 (WSDL) 文件。此文件将向构建客户机提供说明,以便了解服务预期和返回的消息。

在本教程中,您将了解以下内容:

  • WSDL 文件为何重要

  • 可以如何处理 WSDL 文件

  • XML 模式(出现在 WSDL 文件中)的基本知识

  • 如何设计 WSDL 文件的结构

  • WSDL 1.1 和 WSDL 2.0 的基本差异

  • 如何自动从表示服务的 Java 类自动生成 WSDL 文件

  • 如何从 WSDL 文件生成表示服务的 Java 类

  • 如何从 WSDL 文件生成 Web 服务客户机

Classifieds Department 将构建一个服务来接收新广告、编辑和显示现有广告,还能结束一个主题,使其不再接受任何广告。他们将同时使用请求/响应和单向消息传递。

回页首

工具和先决条件

本教程的大部分内容都是概念性的,但为了处理整个教程中的代码,您将需要安装以下软件:

Apache Geronimo 或其他应用服务器——团队将在本教程中创建新 Web 服务,您将需要可在其上运行服务的应用程序。当然,由于 Web 服务应当可互操作,因此使用何种应用服务器并不重要。本教程演示了 Apache Geronimo 的用法,而此应用服务器也是 IBM 的 WebSphere Community Edition 的基础。还可以使用 WebSphere Application Server 等其他应用服务器。您可以从以下网址下载 Apache Geronimo:http://geronimo.apache.org/downloads.html。有关安装 Geronimo 的更多信息,请参见本系列的第一篇教程“了解 Web 服务规范,第 1 部分:SOAP”。

Apache Axis2 的 0 .95 或更高版本——可以手动创建 SOAP 消息,也可以手动对其进行解释,但手边如果有一个可用实现就会方便得多。本教程将演示 Apache Axis2 的用法,其中包含了各种 SOAP 相关的 API,可极大地简化您的工作。可以从以下网址下载 Apache Axis2:http://ws.apache.org/axis2/download.cgi。本教程使用的是 0.95 版,但应该也能使用更高版本。(请注意,在本系列教程的第 1 部分使用的是 0.94 版,但并未使用该版本对本部分的代码进行测试。)

Java 2 Standard Edition 的 1.4 或更高版本——所有这些工具都是基于 Java 的,本教程中将要构建的服务和客户机也是如此。可以从以下网址下载 J2SE SDK:http://java.sun.com/j2se/1.5.0/download.jsp

另外,还需要 Web 浏览器和文本编辑器,但我想您已经有了这两个工具。如果愿意,还可以使用 Eclipse 之类的 IDE,但由于我们的重点是技术而不是工具,因此我将使用文本编辑器和命令行来编辑和编译文件。

概述

在深入了解细节前,让我们了解一下其总体概貌。

Web 服务回顾

相对于传统编程方法而言,使用 Web 服务的主要优势是互操作性。您可以创建一个分布式系统,让分散在各个位置的计算机能够进行通信,但在大多数情况下,您需要使用限制灵活性的专用中间件。也就是说,发送方和接收方都需要使用相同的软件。

Web 服务提供了基于文本(实际上是基于 XML)的方法来往返传递消息,这意味着应用程序不仅不依赖计算机,而且也不依赖于操作系统和编程语言。只要双方遵循相同的 Web 服务标准,就不受各方运行何种软件的影响。

有多个用于传输此信息的标准,但本系列教程重点讨论的是 SOAP,因为此标准非常灵活,而且能与更高级的标准结合使用。

SOAP 知识巩固

SOAP 消息包括三个主要部分:Header、主体和有效负载(包含在主体中)。请看以下示例:


清单 1. 示例 SOAP 消息
<SOAPenv:Envelope        
        xmlns:SOAPenv="http://schemas.xmlSOAP.org/SOAP/envelope/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
   <SOAPenv:Body>
      <req:getNumberOfArticles xmlns:req="http://daily-moon.com/CMS/">
     <req:category<classifieds>/req:category<
     >/req:getNumberOfArticles< 
   >/SOAPenv:Body<
>/SOAPenv:Envelope<
 
 
 

整个消息称为信封,其内容由 Header 和主体组成。Header 包括有关消息本身的信息(如路由信息),或要由位于发送方和最终接收方之间可能对消息进行处理的“SOAP 中间层”或服务处理的信息。消息的主体中包含“有效负载”,其中包含传递给 Web 服务的实际数据。

在本例中,有效负载为 getNumberOfArticles 元素及其内容。

回页首

WSDL 的用途

创建服务时,通常的原因都是因为希望其他人使用此服务。为了使用服务,需要知道向服务发送什么信息、服务将发送回什么信息以及在何处能找到此服务。当然,可以将这些放入字处理文档中,但相比之下,如果此信息采用标准的、最好为人机均可读的格式,则要有用得多。

WSDL 就提供了这样的标准格式。除了不会造成混淆不清外,其主要优势是,由于 WSDL 是事实标准,且采用 XML 格式,因而可由计算机进行处理,便于自动创建客户机(甚至自动创建服务的框架)。Classifieds Department 将要创建接受和管理 classified 广告的服务,以允许其他人(如作业聚合网站)能更方便地使用此服务,他们同样也将使用 WSDL 文件对其进行描述。

回页首

本教程的任务

在本教程中,您将逐步了解 Daily Moon Classifieds Department 的员工如何创建服务并将其公开给其他人使用,从而了解有关 WSDL 的知识。

首先,Christine 将设计 XML 模式,其中定义可以在 SOAP 消息中出现的数据。接下来,Larry 形成实际 WSDL 文件的结构,以确定将往返传递何种信息。然后,Gene 拿到 WSDL 文件,以其为基础设计表示服务的 Java 类,随后,为了进一步确认,他使用 Axis2 从该 Java 类生成 WSDL 文件,以了解二者的差异。然后,Francis 得到此 WSDL 文件,使用其生成基本 Web 服务和访问该服务的客户机。

现在让我们开始吧。

XML 模式简要介绍
Daily Moon 是一家相当小的报社,因此 Classifieds Department 无法承担专用 DBA 带来的相关支出。所以,Christine 总是将数据库视为“自己的孩子”。了解了 WSDL 的规范后,她决定承担起定义 SOAP 消息中可包含的数据的任务。
验证和选项

在首次尝试 Web 服务之后,员工已经熟悉了 XML,因此 Christine 下一步就要了解有关验证的知识。

XML 验证是确保 XML 的结构和内容符合任何预先定义的要求的过程。最初的时候,这表示要符合文档类型定义(Document Type Definition,DTD);DTD 是从 XML 的前身 SGML 语言保留下来的结构。DTD 并不是很难,但存在一些主要的缺点,缺乏灵活性、缺乏 XML 命名空间支持以及其他问题,因此 XML 社区转而采用其他类型的 XML 模式。

请注意,此处所说的“模式”不是特指的。在这里,我使用的是该术语的一般用法,即文档的具体结构,而不是记录此结构的任何语言。这些 XML 模式的最常见支持是 W3C XML Schema 语言,经常将此语言简称为 XML Schema。(注意首字母大写。)

Christine 知道将使用 XML Schema 构建其 WSDL 1.1 文档,但注意到 WSDL 2.0 还特别支持其他模式,如 RELAX NG 和 Schematron,可以在此处使用这些模式。

回页首

实例文档

Christine 首先从一个与进出系统的预期数据近似的示例文档着手。她以数个现有广告为基础创建了以下 XML:


清单 2. 实例文档
                    Listing 2: the instance document<?xml version="1.0"?><ClassifiedList>   <ClassifiedAd adId="1138">      <content>Vintage 1963 T-Bird.  Less than 300 miles.  Driven by my daughter until I took it away.  Serious inquiresonly. 555-3264 after 7 PM.</content>      <endDate>4/15/2007</endDate>      <startDate>4/1/2007</startDate>   </ClassifiedAd>   <ClassifiedAd adId="2187">      <content>30 ft ladder, only used once.  Willing to letgo for half its worth. Has slight dent near the middle.  Harder than a human head. $150 OBO.</content>      <endDate>4/30/2007</endDate>      <startDate>4/10/2007</startDate>   </ClassifiedAd></ClassifiedList>

此文档称为“实例文档”,代表 XML 模式所定义的数据。在本例中,它包括两个作为 ClassifiedList 元素子项的 ClassifiedAd 元素。每个 ClassifiedAd 都包含一个 adId 属性(此属性与数据库内的主键对应)、广告内容以及广告的开始和结束日期。

她将此文档命名为 classifieds.xml

回页首

基本模式

Christie 下一步将创建基本模式文档,以便对系统进行测试。实际模式本身也是一个 XML 文档,因此她创建了基础文件 classifieds.xsd 和一个 schema 元素,如清单 3 中所示:


清单 3. Schema 元素
                    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"></xsd:schema>

此元素将作为 Christine 创建的所有定义的父项。

回页首

验证文档

测试系统的下一步是形成一个简单的 Java 应用程序,以根据模式验证实例文档。该类所需要进行的工作就是指定验证并对文档进行解析,如清单 4 中所示。


清单 4. ValidateWithSchema 类
import java.io.File;
import java.io.IOException;import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException; public class ValidateWithSchema {     public static void main(String args[]) {        DocumentBuilderFactory dbf =                         DocumentBuilderFactory.newInstance();        dbf.setValidating(true);        dbf.setAttribute(          "http://java.sun.com/xml/jaxp/properties/schemaLanguage",           "http://www.w3.org/2001/XMLSchema");        dbf.setAttribute(          "http://java.sun.com/xml/jaxp/properties/schemaSource",          "classifieds.xsd");        Document doc = null;        try{                   DocumentBuilder parser = dbf.newDocumentBuilder();           doc = parser.parse("classifieds.xml");        } catch (Exception e){           e.printStackTrace();        }    }}



实际进行解析的方法并不十分重要;在本例中,Christine 将使用 DOM 解析器构建应用程序。首先,构建一个工厂,然后指定其创建的所有解析器都应为“验证解析器”。(并不会使用所有解析器进行验证。有些仅验证文档是否“形式良好”,而不会验证其实际结构。)
接下来,指定要使用的模式语言以及模式文档本身。在本例中,Christine 使用的是本地文件,但也可以使用任何可作为 URL 引用的文件。
Christine 保存文件,并对其进行编译,运行该文件后,她得到了以下输出,如清单 5 中所示。

清单 5. 运行空白模式
                    Warning: validation was turned on but an org.xml.sax.ErrorHandler was not set, which is probably not what is desired.  Parser will use a default ErrorHandler to print the first 10 errors.  Please callthe 'setErrorHandler' method to fix this.Error: URI=file:///E:/WSDLFiles/classifieds.xml Line=2: cvc-elt.1: Cannot find the declaration of element 'ClassifiedList'.

Warning 并不重要;Christine 仅希望知道文件是否有效,因此她并不需要任何特定的错误处理功能。Error 很重要,因为这意味着分析器在实际查看模式和验证实例文档。

接下来她需要添加实际的定义。

回页首

创建简单的元素

定义最初的数个元素非常简单。contentendDate 和 startDate 元素包含简单的字符串,如清单 6 中所示。


清单 6. 添加简单的元素
                    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">   <xsd:element type="xsd:string" name="content" />   <xsd:element type="xsd:string" name="endDate" />   <xsd:element type="xsd:string" name="startDate" /></xsd:schema>

在本例中,Christine 定义了三个仅包含文本的元素(与其他元素相对)作为内容。同样,它们都不具有任何属性。XML Schema 推荐标准定义了一系列可用于定义内容的不同类型。例如,可以指定 endDate 和 startDate 值必须为 datetime 类型的值。


创建更为复杂的元素

当然,如果所有元素都这样简单,您可能根本不需要模式了。Christine 将继续定义 ClassifiedList 元素(请参见清单 7)。


清单 7. 定义 ClassifiedList 元素
                    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"><xsd:element name="ClassifiedList">   <xsd:complexType>      <xsd:sequence>         <xsd:element name="ClassifiedAd" maxOccurs="unbounded"                                       type="ClassifiedAdType"/>      </xsd:sequence>   </xsd:complexType></xsd:element><xsd:complexType name="ClassifiedAdType">   <xsd:sequence>       <xsd:element type="xsd:string" name="content" />       <xsd:element type="xsd:string" name="endDate" />       <xsd:element type="xsd:string" name="startDate" />   </xsd:sequence></xsd:complexType></xsd:schema>

首先看最下面,Christine 创建了 ClassifiedAdType complexType。这种元素类型按顺序包含以下内容:一个 content 元素、一个endDate 元素和一个 startDate 元素。回到最上面,她定义 ClassifiedList 元素包含一个元素,而此元素还包含一系列元素。不过,在本例中,它包含零个或更多 ClassifiedAdType 类型的元素,所有这些元素均命名为 ClassifiedAd

到目前为止,仅通过这两个定义,她就已覆盖了文档结构的大部分。现在需要做的就是为 adId 属性添加定义。


添加属性

属性在 complexType 元素中定义,如清单 8 中所示。


清单 8. 添加属性,不过她还可以创建限制性更多的类型。
                    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"><xsd:element name="ClassifiedList">   <xsd:complexType>      <xsd:sequence>         <xsd:element name="ClassifiedAd" maxOccurs="unbounded" type="ClassifiedAdType"/>      </xsd:sequence>   </xsd:complexType></xsd:element><xsd:complexType name="ClassifiedAdType">   <xsd:sequence>       <xsd:element type="xsd:string" name="content" />       <xsd:element type="xsd:string" name="endDate" />       <xsd:element type="xsd:string" name="startDate" />   </xsd:sequence>   <xsd:attribute name="adId" type="xsd:integer" /></xsd:complexType></xsd:schema>


在本例中,Christine 将 adId 属性的内容限制为整数。不过,她还可以创建限制更多的类型。

回页首

使用 simpleTypes

XML Schema 提供了强大的功能,用以处理您的文档可能包含的实际内容。例如,Christine 可以指定 adId 属性一定只能包含大于或等于 1000 的整数(请参见清单 9)。

清单 9. 限制值
                    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">...<xsd:complexType name="ClassifiedAdType">   <xsd:sequence>       <xsd:element type="xsd:string" name="content" />       <xsd:element type="xsd:string" name="endDate" />       <xsd:element type="xsd:string" name="startDate" />   </xsd:sequence>   <xsd:attribute name="adId" type="thousandOrGreater" /></xsd:complexType><xsd:simpleType name="thousandOrGreater">  <xsd:restriction base="xsd:integer">      <xsd:minInclusive value="1000"/>  </xsd:restriction></xsd:simpleType></xsd:schema>


这个定义并不涉及任何元素,因此 Christine 使用了 simpleType 而不是 complexType。她为定义为整数的基本类型创建了一个restriction(一种对数据类型的限制)。她为该 restriction 添加了一个“facet”,指定最小值为 1000。XML Schema 定义了大量的此类 facet 以供使用。创建了此类型后,她就可以在属性定义中对其进行引用了。因为已经定义了数据,Christine 随后将此模式交给 Larry,供他用于构建实际的 WSDL。

创建 WSDL 文档

Christine 负责实际的数据,而 Larry 负责在服务及其客户机间传递的实际消息。为了处理这些消息,他创建了 WSDL 文档。

消息

Larry 的第一步是决定服务将实际执行的函数,然后再为服务定义消息。与部门的其他人商议后,他制订了函数及其对应消息的列表,我们将在这一部分的其余内容中讨论这些函数。

createNewAd

此函数在广告结束日期接受广告的内容,并返回新创建的广告的内容(请参见清单 10 和清单 11)。


清单 10. createNewAdRequest(输入)
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <req:createNewAdRequest               xmlns:req="http://daily-moon.com/classifieds/">     <req:content>Vintage 1963 T-Bird...</req:content>     <req:endDate>4/30/07</req:endDate>  </req:createNewAdRequest> </env:Body>


清单 11. createNewAdResponse(输出)
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <res:createNewAdResponse               xmlns:res="http://daily-moon.com/classifieds/">      <res:newAdId>1138</res:newAdId>  </res:createNewAdResponse> </env:Body>

editExistingAd

此函数接受现有广告,并替换其在数据库中的内容。它将返回一个 Boolean 值声明,指示操作是否成功(请参见清单 12 和清单 13)。


清单 12. editExistingAdRequest(输入)
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <req:editExistingAdRequest              xmlns:req="http://daily-moon.com/classifieds/">      <req:ClassifiedAd>         <req:id>1138</req:id>         <req:content>Vintage 1963 T-Bird...</req:content>         <req:startDate>4/1/2007</req:startDate>         <req:endDate>4/30/2007</req:endDate>      <req:ClassifiedAd>  </req:editExistingAdRequest > </env:Body>


清单 13. editExistingAdResponse(输出) 
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <res:editExistingAdResponse              xmlns:res="http://daily-moon.com/classifieds/">      <res:isOK>true</res:isOK>  </res:editExistingAdResponse> </env:Body>

getExistingAds

此函数将返回现有 ClassifiedAds 的列表(请参见清单 14 和清单 15)。


清单 14. getExistingAdsRequest(输入)
                     <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <req:getExistingAdsRequest                   xmlns:req="http://daily-moon.com/classifieds/">  </req:getExistingAdsRequest> </env:Body>


清单 15. getExistingAdsResponse(输出) 
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <res:getExistingAdsResponse              xmlns:res="http://daily-moon.com/classifieds/">    <res:ClassifiedList>      <res:ClassifiedAd>         <res:id>1138</res:id>         <res:content>Vintage 1963 T-Bird...</res:content>         <res:startDate>4/1/2007</res:startDate>         <res:endDate>4/30/2007</res:endDate>      </res:ClassifiedAd>      <res:ClassifiedAd>         <res:id>2883</res:id>         <res:content>Championship playoff tickets...</res:content>         <res:startDate>4/1/2007</res:startDate>         <res:endDate>4/30/2007</res:endDate>      </res:ClassifiedAd>    </res:ClassifiedList>  </res:getExistingAdsResponse ></env:Body>

finalizeIssue

此函数是“仅输入”的函数,接收最终的发布日期,而并不返回任何数据(请参见清单 16)。


清单 16. finalizeIssueRequest(输入) 
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <req:finalizeIssueRequest                   xmlns:req="http://daily-moon.com/classifieds/">     <req:issueDate>4/10/06</req:issueDate>  </req:FinalizeIssueRequest > </env:Body>

有了这些消息后,Larry 开始着手创建实际的 WSDL 文档。

回页首

创建基本文档

Larry 首先创建 WSDL 文档的基本框架(请参见清单 17)。


清单 17. 基本 WSDL 文档
                    <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"        targetNamespace="http://ws.apache.org/axis2"><wsdl:types></wsdl:types><wsdl:message name="createNewAdRequestMessage"></wsdl:message><wsdl:portType name="ClassifiedServicePortType"></wsdl:portType><wsdl:binding name="ClassifiedServiceBinding"></wsdl:binding><wsdl:service name="ClassifiedService"></wsdl:service></wsdl:definitions>

像其所定义的 SOAP 消息一样,WSDL 也是由 XML 组成的。定义位于 definitions 元素内,如此处所示。首先看下面,您定义了serviceservice 使用一个特殊的 binding,而后者是 portType 的一个实现。portType 定义操作,而操作由 messages 组成。消息中包含由 types 部分中定义的 XML 组成。

definitions 元素定义两个命名空间。第一个使用前缀 wsdl:,为组成 WSDL 的实际元素提供命名空间。第二个 targetNamespace 定义 WSDL 定义的项所属的命名空间。

Larry 首先定义的是类型。

回页首

定义类型

Larry 拿到 Christine 提供的定义,并将其放入 types 元素中,从而在文档内创建了一个新模式,如清单 18 中所示。


清单 18. 定义类型
                    <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"        xmlns:ns1="http://org.apache.axis2/xsd"        targetNamespace="http://ws.apache.org/axis2"><wsdl:types>  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"          targetNamespace="http://org.apache.axis2/xsd"          elementFormDefault="unqualified"          attributeFormDefault="unqualified">    <xs:element type="ns1:ClassifiedAd" name="ClassifiedAd" />    <xs:complexType name="ClassifiedAd">      <xs:sequence>        <xs:element type="xs:int" name="id" />        <xs:element type="xs:string" name="content" />        <xs:element type="xs:string" name="endDate" />        <xs:element type="xs:string" name="startDate" />      </xs:sequence>    </xs:complexType>    <xs:element type="ns1:ClassifiedList" name="ClassifiedList" />    <xs:complexType name="ClassifiedList">      <xs:sequence>        <xs:element minOccurs="0" type="ns1:ClassifiedAd"                         name="ClassifiedAd" maxOccurs="unbounded" />      </xs:sequence>    </xs:complexType>    <xs:element name="createNewAdRequest">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:string" name="content" />          <xs:element type="xs:string" name="endDate" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="createNewAdResponse">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:int" name="newAdId" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="editExistingAdRequest">      <xs:complexType>        <xs:sequence>          <xs:element type="ns1:ClassifiedAd" name="ClassifiedAd" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="editExistingAdResponse">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:boolean" name="isOK" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="getExistingAdsRequest">      <xs:complexType />    </xs:element>    <xs:element name="getExistingAdsResponse">      <xs:complexType>        <xs:sequence>          <xs:element type="ns1:ClassifiedList"                                    name="ClassifiedList" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="finalizeIssueRequest">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:string" name="issueDate" />        </xs:sequence>      </xs:complexType>    </xs:element>  </xs:schema></wsdl:types></wsdl:definitions>

从代码的起始处开始,请注意我们有两个命名空间部分。第一个位于 schema 元素本身中。此处 Larry 定义了两个命名空间。第一个命名空间是 XML Schema 命名空间,使用前缀 xs:。第二个命名空间是 targetNamespace,定义模式创建的定义所属的命名空间。也就是说,当第二个版本创建名为 ClassifiedAd  complexType 时,该定义属于命名空间 http://org.apache.axis2/xsd。不过,为了引用该命名空间,Larry 需要创建另一个别名。您可以在以 ns1: 为前缀的 definitions 元素上看到此别名。(Larry 可以直接将该定义放在 schema 元素上,但他稍后需要在 schema 元素外使用此定义。)后面两个属性 elementFormDefault  attributeFormDefault表示元素和属性是否应该有命名空间前缀。

前面四个定义来自 Christine 提供给 Larry 的定义,但其余定义用于定义 Larry 前面创建的消息。

回页首

有关命名空间的说明

和许多编程语言一样,在 XML 中,经常有必要为各种元素和属性指定“命名空间”。这样就能方便地对具有相同名称但用途不同(来源也可能不同)的元素进行区分。XML 通过 URI 引用命名空间。例如,XML 模式命名空间为 http://www.w3.org/2001/XMLSchema。不过,为了方便起见,还为其分配了一个别名(或前缀)。例如,此处的模式命名空间的前缀为 xs:。请记住,别名只是一个别名而已,重要的是 URI。因此,属于 ns1: 命名空间的元素或属性也是模式的 targetNamespace 的一部分。

回页首

创建消息

定义了各个类型后,Larry 就可以定义将在 SOAP 信封中来回传递的消息了(请参见清单 19)。


清单 19. 创建消息
                    <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"        xmlns:ns1="http://org.apache.axis2/xsd"        targetNamespace="http://ws.apache.org/axis2"><wsdl:types>  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"          targetNamespace="http://org.apache.axis2/xsd"          elementFormDefault="unqualified"          attributeFormDefault="unqualified">...    <xs:element name="createNewAdRequest">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:string" name="content" />          <xs:element type="xs:string" name="endDate" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="createNewAdResponse">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:int" name="newAdId" />        </xs:sequence>      </xs:complexType>    </xs:element>...  </xs:schema></wsdl:types><wsdl:message name="createNewAdRequestMessage">  <wsdl:part name="part1" element="ns1:createNewAdRequest" /></wsdl:message><wsdl:message name="createNewAdResponseMessage">  <wsdl:part name="part1" element="ns1:createNewAdResponse" /></wsdl:message><wsdl:message name="getExistingAdsResponseMessage">  <wsdl:part name="part1" element="ns1:getExistingAdsResponse" /></wsdl:message><wsdl:message name="editExistingAdRequestMessage">  <wsdl:part name="part1" element="ns1:editExistingAdRequest" /></wsdl:message><wsdl:message name="getExistingAdsRequestMessage">  <wsdl:part name="part1" element="ns1:getExistingAdsRequest" /></wsdl:message><wsdl:message name="editExistingAdResponseMessage">  <wsdl:part name="part1" element="ns1:editExistingAdResponse" /></wsdl:message><wsdl:message name="finalizeIssueRequestMessage">  <wsdl:part name="part1" element="ns1:finalizeIssueRequest" /></wsdl:message><wsdl:portType name="ClassifiedServicePortType"></wsdl:portType><wsdl:binding name="ClassifiedServiceBinding"></wsdl:binding><wsdl:service name="ClassifiedService"></wsdl:service></wsdl:definitions>

每个消息都具有一个 name,以便稍后对其进行引用。在每个消息内,您可以定义一个或多个 part。请注意,WSDL 2.0 仅允许每个message 包含一个 part,因此 Larry 此处严格遵循了此约定。

每个 part 都有一个 name,而由 element 的名称组成该 part。元素名称将反过来引用在 types 元素中定义的类型。请注意,元素的名称以 ns1: 为前缀,即与模式的 targetNamespace 匹配的命名空间前缀。也就是说,当 Larry 创建 createNewAdResponse 的定义时,该定义进入了 http://org.apache.axis2/xsd namespace 中,由于前缀 ns1: 也引用此命名空间,因此现在可以使用该前缀引用此命名空间。

回页首

定义接口 (portType)

消息本身并不会带来什么好处,因此 Larry 需要将其与特定的操作关联。这些 operation 属于 portType 的一部分。portType 仅包含定义,而不包含实现。就这个意义而言,它与接口很相似。事实上,在 WSDL 2.0 中,portType 的名称已更改为 interface。Larry 为每个功能创建了一个操作(请参见清单 20)。


清单 20. 添加操作
                    <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"        xmlns:tns=http://ws.apache.org/axis2       xmlns:ns1="http://org.apache.axis2/xsd"        targetNamespace="http://ws.apache.org/axis2"><wsdl:types>...</wsdl:types><wsdl:message name="createNewAdRequestMessage">  <wsdl:part name="part1" element="ns1:createNewAdRequest" /></wsdl:message><wsdl:message name="createNewAdResponseMessage">  <wsdl:part name="part1" element="ns1:createNewAdResponse" /></wsdl:message>...<wsdl:message name="finalizeIssueRequestMessage">  <wsdl:part name="part1" element="ns1:finalizeIssueRequest" /></wsdl:message><wsdl:portType name="ClassifiedServicePortType">  <wsdl:operation name="finalizeIssue">    <wsdl:input message="tns:finalizeIssueRequestMessage" />  </wsdl:operation>  <wsdl:operation name="createNewAd">    <wsdl:input message="tns:createNewAdRequestMessage" />    <wsdl:output message="tns:createNewAdResponseMessage" />  </wsdl:operation>  <wsdl:operation name="editExistingAd">    <wsdl:input message="tns:editExistingAdRequestMessage" />    <wsdl:output message="tns:editExistingAdResponseMessage" />  </wsdl:operation>  <wsdl:operation name="getExistingAds">    <wsdl:input message="tns:getExistingAdsRequestMessage" />    <wsdl:output message="tns:getExistingAdsResponseMessage" />  </wsdl:operation></wsdl:portType><wsdl:binding name="ClassifiedServiceBinding"></wsdl:binding><wsdl:service name="ClassifiedService"></wsdl:service></wsdl:definitions>

Larry 创建了两种类型的消息。一类消息(如 finalizeIssue)是“仅输入”的操作,因为它仅具有输入消息。消息finalizeIssueRequest 是前面定义的,和前面一样,我们将创建一个新前缀 tns: 来引用其所属的命名空间。

第二种操作类型是请求/响应操作,如 createNewAd。在这种情况下,Larry 将同时定义输入和输出消息。

回页首

定义绑定

如果 portType 像接口,则 binding 为该接口的实现(请参见清单 21)。


清单 21. 创建绑定
                    <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"        xmlns:tns="http://ws.apache.org/axis2"       xmlns:ns1="http://org.apache.axis2/xsd"        targetNamespace="http://ws.apache.org/axis2"><wsdl:types></wsdl:types><wsdl:message name="createNewAdRequestMessage">  <wsdl:part name="part1" element="ns1:createNewAdRequest" /></wsdl:message>...<wsdl:portType name="ClassifiedServicePortType">  <wsdl:operation name="finalizeIssue">    <wsdl:input message="tns:finalizeIssueRequestMessage" />  </wsdl:operation>  <wsdl:operation name="createNewAd">    <wsdl:input message="tns:createNewAdRequestMessage" />    <wsdl:output message="tns:createNewAdResponseMessage" />  </wsdl:operation>  <wsdl:operation name="editExistingAd">    <wsdl:input message="tns:editExistingAdRequestMessage" />    <wsdl:output message="tns:editExistingAdResponseMessage" />  </wsdl:operation>  <wsdl:operation name="getExistingAds">    <wsdl:input message="tns:getExistingAdsRequestMessage" />    <wsdl:output message="tns:getExistingAdsResponseMessage" />  </wsdl:operation></wsdl:portType><wsdl:binding name="ClassifiedServiceBinding"                          type="tns:ClassifiedServicePortType">  <soap:binding transport="http://schemas.xmlsoap.org/soap/http"                                                 style="document" />  <wsdl:operation name="createNewAd">    <soap:operation soapAction="createNewAd" style="document" />    <wsdl:input>      <soap:body use="literal"                       namespace="http://daily-moon.com/classifieds" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                       namespace="http://daily-moon.com/classifieds" />    </wsdl:output>  </wsdl:operation>  <wsdl:operation name="finalizeIssue">    <soap:operation soapAction="finalizeIssue" style="document" />    <wsdl:input>      <soap:body use="literal"                       namespace="http://daily-moon.com/classifieds" />    </wsdl:input>  </wsdl:operation>  <wsdl:operation name="editExistingAd">    <soap:operation soapAction="editExistingAd" style="document" />    <wsdl:input>      <soap:body use="literal"                       namespace="http://daily-moon.com/classifieds" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                       namespace="http://daily-moon.com/classifieds" />    </wsdl:output>  </wsdl:operation>  <wsdl:operation name="getExistingAds">    <soap:operation soapAction="getExistingAds" style="document" />    <wsdl:input>      <soap:body use="literal"                       namespace="http://daily-moon.com/classifieds" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                       namespace="http://daily-moon.com/classifieds" />    </wsdl:output>  </wsdl:operation></wsdl:binding><wsdl:service name="ClassifiedService"></wsdl:service></wsdl:definitions>

首先,请注意绑定 type 引用 Larry 已经创建的 ClassifiedServicePortType。其次,请注意添加了 soap: 命名空间。此 binding 用于 HTTP 上的 SOAP 消息,如 soap:binding 元素中所示。请注意 transport 属性。(尚不用担心 style,我们将在接下来的部分中对其进行讨论。)

对于共享其在 portType 中的名称的每个操作,Larry 现在都要添加一个 soap:operation 元素。此元素执行两个操作。根据此元素的说明,第一个操作实际上是 SOAP 操作。第二个操作用于指定 soapAction,此为在实际 SOAP 消息前发送的 HTTP Header。服务器可以使用此 Header 来将请求路由到相应的操作。

请注意,soapAction 始终有一定的问题,虽然在 WSDL 1.1 是可选的,但已经完全从 WSDL 2.0 中删除了。

每个操作还定义 input  output 消息(或者,使用仅输入的消息时,只定义 input 消息),但现在 Larry 还将添加特定的 SOAP 信息。同样,不用担心 use 属性;我们也将在稍后讨论此属性。此元素还为有效负载的内容定义命名空间。

回页首

定义服务

定义了接口和绑定后,接下来就要定义服务了(请参见清单 22)。


清单 22. 定义服务
                    ...<wsdl:binding name="ClassifiedServiceBinding"                      type="tns:ClassifiedServicePortType">...</wsdl:binding><wsdl:service name="ClassifiedService">  <wsdl:port name="ClassifiedServicePort"                     binding="tns:ClassifiedServiceBinding">    <soap:address location= "http://127.0.0.1:8080/axis2/services/ClassifiedService" />  </wsdl:port></wsdl:service></wsdl:definitions>

service 可以具有多个端点,每个端点都由其自己对应的 port 元素进行定义。port 元素与特定绑定对应,包括有关如何访问此绑定的信息。在本例中,Larry 指定可以通过 SOAP 在以下位置访问该端口:http://127.0.0.1:8080/axis2/services/ClassifiedService

回页首

为服务添加注释

定义了服务后,还要进行一个步骤。虽然是可选的,但真的应该进行此步骤。WSDL 允许您向 WSDL 文档中的任何元素添加 documentation 元素。清单 23 提供了一个示例。


清单 23. 为服务添加注释
                    ...<wsdl:portType name="ClassifiedServicePortType">  <wsdl:document>The finalizeIssue operation is an "in-only"         operation that tells the system not to accept any          more ads for a particular date.</wsdl:document>  <wsdl:operation name="finalizeIssue">    <wsdl:input message="tns:finalizeIssueRequestMessage" />  </wsdl:operation>...

有关 WSDL 的知识还有很多,但上面这些是基本的内容。不过,继续学习本教程之前,让我们了解一些您可能遇到的更为复杂的情况。

回页首

绑定 SOAP over HTTP 之外的其他协议

尽管 WSDL 的最常见用法是定义 SOAP over HTTP,但却不是指定 Web 服务的唯一方法。例如,Larry 可以创建一个使用 SOAP over E-mail 的绑定,并仅为 finalizeIssue 操作启用它(请参见清单 24)。


清单 24. 添加其他绑定
                    ...<wsdl:binding name="ClassifiedServiceEmailBinding"                           type="tns:ClassifiedServicePortType">   <soap:binding style="document" transport="http://example.com/smtp"/>   <wsdl:operation name="finalizeIssue">     <wsdl:input message="tns:finalizeIssueRequestMessage">         <soap:body parts="body" use="literal"/>      </wsdl:input>   </wsdl:operation></wsdl:binding><wsdl:service name="ClassifiedService">  <wsdl:port name="ClassifiedServicePort"                     binding="tns:ClassifiedServiceBinding">    <soap:address location=       "http://127.0.0.1:8080/axis2/services/ClassifiedService" />  </wsdl:port>  <wsdl:port name="ClassifiedServiceEmailPort"                        binding="tns:ClassifiedServiceEmailBinding">    <soap:address location="mailto:finalizeIssue@daily-moon.com" />  </wsdl:port></wsdl:service></wsdl:definitions>

此处 Larry 添加了第二个绑定 ClassifiedServiceEmailBinding。和 ClassifiedServiceBinding 绑定一样,此绑定基于ClassifiedServicePortType portType。不过,在本例中,他使用的是 SMTP(用于发送电子邮件的协议),而不是 HTTP。他可以随后向服务添加第二个端口,使其基于新绑定,使用电子邮件 URL 作为地址,而不是 HTTP URL。您可以为单个服务创建任意多的端口。

回页首

SOAP 和 Mime

另一个与此解决方案有关的主题还不甚明显,即将 SOAP 用于附件。由于消息不仅是 XML 文档,您应该如何使用 WSDL 进行指定呢?这个问题的答案涉及到一个事实,即消息是使用“多部分 MIME”发送的。幸运的是,您可以在 WSDL 文档中指定这一点。例如,Larry 希望指定一个操作,通过此操作,用户可以编辑从广告的 PDF 校样获得的现有广告(请参见清单 25)。


>清单 25. 指定包含多个部分的消息
                    ...  <wsdl:operation name="editExistingAd">    <soap:operation soapAction="editExistingAd" style="document" />    <wsdl:input>      <soap:body use="literal"                 namespace="http://daily-moon.com/classifieds" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                 namespace="http://daily-moon.com/classifieds" />      <mime:multipartRelated>          <mime:part>             <soap:body parts="body" use="literal"/>          </mime:part>          <mime:part>              <mime:content part="docs" type="text/html"/>          </mime:part>          <mime:part>              <mime:content part="proof" type="image/gif"/>              <mime:content part="proof" type="image/jpeg"/>           </mime:part>       </mime:multipartRelated>    </wsdl:output>...

在本例中,返回消息包含 SOAP、一个 HTML 文档和两个图像文件。

回页首

WSDL 2.0 中包含的新内容

在此部分中,我尽力说明 WSDL 2.0 与 WSDL 1.1 的一些不同之处,如从可能出现多个 portType 的情况变为单个 interface,以及message 不再能包含多个部分等。其他方面也有一定程度的调整。

有多个因素促成了这些更改,但大部分更改的目的都是为了提高互操作性——不符合 WS-I 基本概要 (WS-I Basic Profile) 的构造通常都禁止使用——或为了更方便地将 WSDL 与经过扩展的 SOAP 规范一起使用。例如,WSDL 2.0 更多使用的是“组件”模型,此模型更适合与 WS-Choreography 等规范的要求进行集成。

另一个更改与“消息交换模式”的正式规范有关。WSDL 2.0 不依赖于用户确定是同时存在输入和输出还是仅有一个输入消息,它允许您具体地声明所使用的模式(请参见清单 26)。


清单 26. 指定消息交换模式
                    ...  <wsdl:operation name="finalizeIssue"            pattern=http://www.w3.org/2006/01/wsdl/in-only">    <wsdl:input message="tns:finalizeIssueRequestMessage" />  </wsdl:operation>  <wsdl:operation name="createNewAd"              pattern="http://www.w3.org/2006/01/wsdl/in-out">    <wsdl:input message="tns:createNewAdRequestMessage" />    <wsdl:output message="tns:createNewAdResponseMessage" />  </wsdl:operation>...

现在让我们深入了解一下 WSDL 样式。

WSDL 样式

对于刚刚学习 WSDL 的开发人员而言,最容易混淆的是为文档选择样式。让我们了解一下不同的选项及其对 WSDL 和 SOAP 文档的影响。

编程样式和编码

在 XML 世界中,通常有两种类型的人:将 XML 视为数据格式的人和将 XML 视为文档标记的人。通常,二者永远对立。在某种程度上,这种对立也进入了 Web 服务中。有人将其视为基于 XML 的远程过程调用 (Remote Procedure Call),还有些人将其视为将 XML 信息从一个位置传递到另一个位置的方法。

对 WSDL 而言,这会在选择消息的“样式”时表现出来。创建绑定时,可以选择 Document 样式(如 Larry 为 Classifieds 服务就是选择的此样式)或 RPC 样式。这两个样式并不一定就“正确”或“错误”。但二者均具有自己的优点和缺点。

使用 RPC 样式时,要执行的方法的名称同时也是有效负载的根元素的名称。您可能会说,等一等。Classifieds WSDL 的结构不就是这样吗?是,也不是。之所以说是,是因为根元素的名称与我们希望服务执行的方法的名称一样。不过,从某种意义而言,这只是巧合;Larry 是有意这样设计服务的。

让我们看看不同的选择及其在 WSDL 和 SOAP 方面的表现。

Document/Literal

Document/Literal 样式意味着有效负载仅包含要向服务传递的实际数据。任何将消息发送到其目的地所必需的路由均以其他方式来完成,如通过 soapAction Header 或服务的特定 URL。

这样的消息简单而直接(请参见清单 27)。


清单 27. Document/Literal 消息
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><env:Body>     <req:content>Vintage 1963 T-Bird...</req:content>     <req:endDate>4/30/07</req:endDate></env:Body></env:Envelope>

请注意,有效负载没有根元素。在 WSDL 文件中,您直接定义相应的元素并将其添加到消息中(请参见清单 28)。


清单 28. Document/Literal WSDL
                    ...<wsdl:types>  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"          targetNamespace="http://org.apache.axis2/xsd"          elementFormDefault="unqualified"          attributeFormDefault="unqualified">...    <xs:element type="xs:string" name="content" />    <xs:element type="xs:string" name="endDate" />...  </xs:schema></wsdl:types><wsdl:message name="createNewAdRequestMessage">  <wsdl:part name="part1" element="ns1:content" />  <wsdl:part name="part2" element="ns1:endDate" /></wsdl:message>...<wsdl:portType name="ClassifiedServicePortType">...  <wsdl:operation name="createNewAd">    <wsdl:input message="tns:createNewAdRequestMessage" />    <wsdl:output message="tns:createNewAdResponseMessage" />  </wsdl:operation>...</wsdl:portType>...<wsdl:binding name="ClassifiedServiceBinding" type="tns:ClassifiedServicePortType">  <soap:binding transport="http://schemas.xmlsoap.org/soap/http"     style="document" />  <wsdl:operation name="createNewAd">    <soap:operation soapAction="createNewAd" style="document" />    <wsdl:input>      <soap:body use="literal" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal" />    </wsdl:output>  </wsdl:operation>...</wsdl:binding>...

请注意,在 Document 样式中,属于有效负载的所有元素都在模式中具有相应的定义。另请注意,此消息具有两个截然不同的部分,每个部分各自引用一个特定的元素。

回页首

Wrapped 样式

本教程使用 Document/Literal/Wrapped 样式。此样式与 Document/Literal 样式类似,不过其有效负载具有根元素。这具有包含要执行的方法的名称的优点(虽然这并不是必需的),同时也符合 WS-I 基本概要的要求。为了便于理解,下面提供了一个简单的消息(请参见清单 29)。


清单 29. Document/Literal/Wrapped 消息
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <req:createNewAdRequest               xmlns:req="http://daily-moon.com/classifieds/">     <req:content>Vintage 1963 T-Bird...</req:content>     <req:endDate>4/30/07</req:endDate>  </req:createNewAdRequest> </env:Body></env:Envelope>

为了保持完整性,下面提供了相关的 WSDL(请参见清单 30)。


清单 30. Document/literal/Wrapped WSDL
                    ...<wsdl:types>  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"          targetNamespace="http://org.apache.axis2/xsd"          elementFormDefault="unqualified"          attributeFormDefault="unqualified">...    <xs:element name="createNewAdRequest">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:string" name="content" />          <xs:element type="xs:string" name="endDate" />        </xs:sequence>      </xs:complexType>    </xs:element>...  </xs:schema></wsdl:types><wsdl:message name="createNewAdRequestMessage">  <wsdl:part name="part1" element="ns1:createNewAdRequest" /></wsdl:message>...<wsdl:portType name="ClassifiedServicePortType">  <wsdl:operation name="createNewAd">    <wsdl:input message="tns:createNewAdRequestMessage" />    <wsdl:output message="tns:createNewAdResponseMessage" />  </wsdl:operation>...</wsdl:portType>...<wsdl:binding name="ClassifiedServiceBinding" type="tns:ClassifiedServicePortType">  <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />  <wsdl:operation name="createNewAd">    <soap:operation soapAction="createNewAd" style="document" />    <wsdl:input>      <soap:body use="literal"                         namespace="http://ws.apache.org/axis2" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                         namespace="http://ws.apache.org/axis2" />    </wsdl:output>  </wsdl:operation>...</wsdl:binding>...

同样,有效负载中的所有元素都在模式中定义。

回页首

RPC/Literal

RPC 样式进行处理的方式略为不同。进入消息中的是 WSDL 的相应内容,而不是模式中的内容。例如,请看以下的 SOAP 消息(请参见清单 31)。


清单 31. RPC/Literal 消息
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>  <req:createNewAdRequest               xmlns:req="http://daily-moon.com/classifieds/">     <req:content>Vintage 1963 T-Bird...</req:content>     <req:endDate>4/30/07</req:endDate>  </req:createNewAdRequest> </env:Body></env:Envelope>

消息本身与 Document/Literal/Wrapped 样式一样,但 WSDL 大不相同(请参见清单 32)。


清单 32. RPC/Literal 消息的 WSDL
                    ...<wsdl:types>  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"          targetNamespace="http://org.apache.axis2/xsd"          elementFormDefault="unqualified"          attributeFormDefault="unqualified">...  </xs:schema></wsdl:types><wsdl:message name="createNewAdRequest">  <wsdl:part name="content" element="xsd:string" />  <wsdl:part name="endDate" element="xsd:string" /></wsdl:message>...<wsdl:portType name="ClassifiedServicePortType">  <wsdl:operation name="createNewAd">    <wsdl:input message="tns:createNewAdRequest" />    <wsdl:output message="tns:createNewAdResponseMessage" />  </wsdl:operation>...</wsdl:portType>...<wsdl:binding name="ClassifiedServiceBinding" type="tns:ClassifiedServicePortType">  <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />  <wsdl:operation name="createNewAd">    <soap:operation soapAction="createNewAd" style="rpc" />    <wsdl:input>      <soap:body use="literal"                         namespace="http://ws.apache.org/axis2" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                         namespace="http://ws.apache.org/axis2" />    </wsdl:output>  </wsdl:operation>...</wsdl:binding>...

首先,请注意模式中并未定义任何实际内容。相反,message 指定要执行的方法的名称,而 message part 直接定义每个元素。另请注意,在 RPC 样式中,消息部分的名称很重要;此名称是有效负载内的元素的名称。消息类型是直接定义的。(当然,这意味着您无法在有效负载中包含复杂元素,但由于此样式用于模拟远程过程调用,因此这并不是一个问题。)

 portType 中,当您指定消息时,可以直接引用与这些元素一起创建的消息。然后,在绑定中,通过指定 RPC 样式,可以清楚地确定如何将所有这些内容转换为 SOAP 消息。

回页首

RPC/Encoded

我们要了解的最后一个样式是 RPC/Encoded。此样式与 RPC/Literal 类似,唯一的不同之处在于由 SOAP 消息定义实际类型信息(请参见清单 33)。


清单 33. RPC/Encoded SOAP 消息
                    <env:Envelope        xmlns:env="http://schemas.xmlSOAP.org/SOAP/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body>   <req:createNewAdRequest               xmlns:req="http://daily-moon.com/classifieds/">     <req:content xsi:type="xs:string">Vintage 1963                                           T-Bird...</req:content>     <req:endDate xsi:type="xs:string">4/30/07</req:endDate>    </req:createNewAdRequest>   </env:Body></env:Envelope>

用于定义此消息的 WSDL 与 RPC/Literal 一样,但向绑定添加了额外的编码信息(请参见清单 34)。


清单 34. RPC/Encoded WSDL
                    <wsdl:definitions xmlns:xs="http://www.w3.org/2001/XMLSchema"        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"        xmlns:tns="http://ws.apache.org/axis2"       xmlns:axis2="http://ws.apache.org/axis2"       xmlns:ns1="http://org.apache.axis2/xsd"         targetNamespace="http://ws.apache.org/axis2"><wsdl:types>  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"          targetNamespace="http://org.apache.axis2/xsd"          elementFormDefault="unqualified"          attributeFormDefault="unqualified">...  </xs:schema></wsdl:types><wsdl:message name="createNewAdRequest">  <wsdl:part name="content" element="xsd:string" />  <wsdl:part name="endDate" element="xsd:string" /></wsdl:message>...<wsdl:portType name="ClassifiedServicePortType">  <wsdl:operation name="createNewAd">    <wsdl:input message="tns:createNewAdRequest" />    <wsdl:output message="tns:createNewAdResponse" />  </wsdl:operation>...</wsdl:portType><wsdl:binding name="ClassifiedServiceBinding" type="tns:ClassifiedServicePortType">  <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc" />  <wsdl:operation name="createNewAd">    <soap:operation soapAction="createNewAd" style="document" />    <wsdl:input>      <soap:body use="encoded" encodingStyle=http://schemas.xmlsoap.org/soap/encoding/  namespace="http://ws.apache.org/axis2" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal" namespace="http://ws.apache.org/axis2" />    </wsdl:output>  </wsdl:operation>...</wsdl:binding>...

手动创建 WSDL 文件时,必须自己处理其中的所有内容。幸运的是,并不一定始终要手动进行创建。

使用 WSDL 生成代码

Gene 和 Francis 是该报社的团队程序员,借调自 IT 部门,Classifieds Department 可以随时调请他们处理自己的项目。他们将通过采用两种方法来进行生成 WSDL 代码的工作;Gene 将重点进行从 Java 到 WSDL 的代码生成工作,而 Francis 将从 WSDL 生成 Java 代码。

代码生成的工作原理

在 WSDL 的早期,最先出现的两个应用程序是 Java2WSDL 和 WSDL2Java。那么,不能用来实现自动化功能的自动化格式究竟有什么好处呢?当然,当时您的选择是有限的。这些代码最初是面向 RPC 样式的,很难自动生成带复杂有效负载的系统。

很快发展到了目前的阶段,这些问题得到了很好的解决。Axis2 几乎可以从任何 WSDL 文档生成 Java 代码,也能从 WSDL 生成 Java 类。它通过数据绑定实现了这个目标,在数据绑定中,XML 结构可以转换为 Java 对象,反之亦然。此生成过程将创建代码,以供随后进行更改、调整、编译和运行。

首先,Gene 从 Java 类开始,使用其生成 WSDL 文档。然后,Francis 获取该文档,并使用其生成服务和客户机。对于服务,此生成过程将创建一个框架,可以在其中添加自己的代码,以执行希望服务执行的操作。对于客户机,它将创建一个存根,以用于像使用 Java 方法一样调用 Web 服务方法。

准备工作

第一步要确保环境已经准备就绪。下载 Apache Axis2 的 0.95 版,对其进行解压缩,然后确保 lib 目录中的所有其他 *.jar 文件都位于 CLASSPATH 上。

要运行 Web 服务,请安装 Apache Geronimo(如果尚未安装)并将其启动。(具体说明,请参见第 1 部分。)下载 Axis2 v0.95 War 分发版并将其复制到 <GERONIMO_HOME>/deploy 目录。Geronimo 将自动部署 Axis2。

回页首

Java 类

Gene 从 ClassifiedService 类着手,他会将此类作为主服务,同时也作为进行测试的方法,以确保所有部分均按预期工作(请参见清单 35)。


清单 35. ClassifiedService.java
                    package org.dailymoon.classifieds;public class ClassifiedService {      public static int createNewAd(String content, String endDate){      ClassifiedAd newAd = new ClassifiedAd();      newAd.setEnd(endDate);      newAd.setContent(content);      newAd.save();      return 1;   }   public static boolean editExistingAd(ClassifiedAd adToEdit){      //Do stuff with the ad here      return true;   }   public static ClassifiedList getExistingAds(){       ClassifiedAd[] listOfAds = {new ClassifiedAd(), new ClassifiedAd(), new ClassifiedAd()};       ClassifiedList listToReturn = new ClassifiedList(listOfAds);       return listToReturn;   }   public static void finalizeIssue(String dateToFinalize){       //Don't return anything.       System.out.println(dateToFinalize + " finalized.");   }   public static void main (String args[]){         ClassifiedService.createNewAd(           "Eclipse experts needed.  Contact Nick for details.",                                                   "4/21/2006");         ClassifiedAd adToEdit = new ClassifiedAd();         adToEdit.setId(1);         adToEdit.setStart("4/8/2006");         adToEdit.setEnd("4/30/2006");         adToEdit.setContent(             "Geronimo experts needed.  Contact Nick for details.");         ClassifiedService.editExistingAd(adToEdit);         ClassifiedList adList = ClassifiedService.getExistingAds();         System.out.println(adList.toString());   }}

此应用程序本身相当简单。它提供了创建新广告、编辑现有广告和列出现有广告的示例。它提供了对 Gene 希望公开的四个方法(createNewAdeditExistingAdgetExistingAds  finalizeIssue)的基本限制。

(请确保在生成 WSDL 前将 main 方法注释掉。此方法不会有负作用,但会生成不必要的额外代码。)

此类还引用其他两个类 ClassifiedAd  ClassifiedList。为了生成过程能够了解如何将这些对象构建为 XML 结构,Gene 将其创建为独立的类(请参见清单 36)。


清单 36. ClassifiedAd.java
                    package org.dailymoon.classifieds;public class ClassifiedAd {   private int id;   private String startDate;   private String endDate;   private String content;   public void setId(int newId){      id = newId;   }   public void setStartDate(String newStart){      startDate = newStart;   }   public void setEndDate(String newEnd){      endDate = newEnd;   }   public void setContent(String newContent){      content = newContent;   }   public void save(){       //Save data here       System.out.println("Ad saved.");   }}

同样,类本身并不完整,但已经具有了相应的结构(请参见清单 37)。


清单 37. ClassifiedList.java
                    package org.dailymoon.classifieds;public class ClassifiedList {    public ClassifiedAd[] listOfAds;    public ClassifiedList(ClassifiedAd[] newListOfAds){        listOfAds = newListOfAds;    }    public ClassifiedAd[] getRawAds(){         return listOfAds;    }    public String toString(){         return "This is a string of results.";    }}

此处,Gene 指定 ClassifiedList 包含一个 ClassifiedAd 对象数组。

有了所有这些类后,他就可以生成 WSDL 了。

回页首

生成 WSDL 并定义消息

创建 WSDL 是一个简单的过程。Gene 从命令行发出相应的命令,如清单 38 中所示:


清单 38. 用于生成 WSDL 的命令
                    java org.apache.axis2.wsdl.Java2WSDL -cn         org.dailymoon.classifieds.ClassifiedService -o

(请注意,此命令应该全部在一行输入。)

-cn 开关指定形成服务基础的类。-o 开关指定输出目录。如果没有出现问题,此类将以静默方式执行,在输出文件中生成 ClassifiedService.wsdl 文件。此文件与 Larry 前面生成的文件很相似——它们设计为在相同的服务上工作——但需要进行一些小更改,以调整生成过程中采用一般方式命名的项。具体来说,不一定始终正确地进行参数转换,将可能必须进行重命名。

以下是生成的 WSDL 文件,其中调整的部分以黑体显示(请参见清单 39)。


清单 39. WSDL 文件
                    <wsdl:definitions xmlns:xs="http://www.w3.org/2001/XMLSchema"        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"        xmlns:tns="http://ws.apache.org/axis2"       xmlns:ns1="http://org.apache.axis2/xsd"        targetNamespace="http://ws.apache.org/axis2"><wsdl:types>  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"          targetNamespace="http://org.apache.axis2/xsd"          elementFormDefault="unqualified"          attributeFormDefault="unqualified">    <xs:element type="ns1:ClassifiedAd" name="ClassifiedAd" />    <xs:complexType name="ClassifiedAd">      <xs:sequence>        <xs:element type="xs:int" name="id" />        <xs:element type="xs:string" name="content" />        <xs:element type="xs:string" name="endDate" />        <xs:element type="xs:string" name="startDate" />      </xs:sequence>    </xs:complexType>    <xs:element type="ns1:ClassifiedList" name="ClassifiedList" />    <xs:complexType name="ClassifiedList">      <xs:sequence>        <xs:element minOccurs="0" type="ns1:ClassifiedAd"                         name="ClassifiedAd" maxOccurs="unbounded" />      </xs:sequence>    </xs:complexType>    <xs:element name="createNewAdRequest">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:string" name="content" />          <xs:element type="xs:string" name="endDate" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="createNewAdResponse">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:int" name="newAdId" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="editExistingAdRequest">      <xs:complexType>        <xs:sequence>          <xs:element type="ns1:ClassifiedAd" name="existingAd" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="editExistingAdResponse">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:boolean" name="wasSuccessful"/>        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="getExistingAdsRequest">      <xs:complexType />    </xs:element>    <xs:element name="getExistingAdsResponse">      <xs:complexType>        <xs:sequence>          <xs:element type="ns1:ClassifiedList" name="ClassifiedList" />        </xs:sequence>      </xs:complexType>    </xs:element>    <xs:element name="finalizeIssueRequest">      <xs:complexType>        <xs:sequence>          <xs:element type="xs:string" name="issueToFinalize" />        </xs:sequence>      </xs:complexType>    </xs:element>  </xs:schema></wsdl:types><wsdl:message name="createNewAdRequestMessage">  <wsdl:part name="part1" element="ns1:createNewAdRequest" /></wsdl:message><wsdl:message name="createNewAdResponseMessage">  <wsdl:part name="part1" element="ns1:createNewAdResponse" /></wsdl:message><wsdl:message name="getExistingAdsResponseMessage">  <wsdl:part name="part1" element="ns1:getExistingAdsResponse" /></wsdl:message><wsdl:message name="editExistingAdRequestMessage">  <wsdl:part name="part1" element="ns1:editExistingAdRequest" /></wsdl:message><wsdl:message name="getExistingAdsRequestMessage">  <wsdl:part name="part1" element="ns1:getExistingAdsRequest" /></wsdl:message><wsdl:message name="editExistingAdResponseMessage">  <wsdl:part name="part1" element="ns1:editExistingAdResponse" /></wsdl:message><wsdl:message name="finalizeIssueRequestMessage">  <wsdl:part name="part1" element="ns1:finalizeIssueRequest" /></wsdl:message><wsdl:portType name="ClassifiedServicePortType">  <wsdl:operation name="finalizeIssue">    <wsdl:input message="tns:finalizeIssueRequestMessage" />  </wsdl:operation>  <wsdl:operation name="createNewAd">    <wsdl:input message="tns:createNewAdRequestMessage" />    <wsdl:output message="tns:createNewAdResponseMessage" />  </wsdl:operation>  <wsdl:operation name="editExistingAd">    <wsdl:input message="tns:editExistingAdRequestMessage" />    <wsdl:output message="tns:editExistingAdResponseMessage" />  </wsdl:operation>  <wsdl:operation name="getExistingAds">    <wsdl:input message="tns:getExistingAdsRequestMessage" />    <wsdl:output message="tns:getExistingAdsResponseMessage" />  </wsdl:operation></wsdl:portType><wsdl:binding name="ClassifiedServiceBinding" type="tns:ClassifiedServicePortType">  <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />  <wsdl:operation name="createNewAd">    <soap:operation soapAction="createNewAd" style="document" />    <wsdl:input>      <soap:body use="literal"                  namespace="http://daily-moon.com/classifieds" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                  namespace="http://daily-moon.com/classifieds" />    </wsdl:output>  </wsdl:operation>  <wsdl:operation name="finalizeIssue">    <soap:operation soapAction="finalizeIssue" style="document" />    <wsdl:input>      <soap:body use="literal"                   namespace="http://daily-moon.com/classifieds" />    </wsdl:input>  </wsdl:operation>  <wsdl:operation name="editExistingAd">    <soap:operation soapAction="editExistingAd" style="document" />    <wsdl:input>      <soap:body use="literal"                    namespace="http://daily-moon.com/classifieds" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                    namespace="http://daily-moon.com/classifieds" />    </wsdl:output>  </wsdl:operation>  <wsdl:operation name="getExistingAds">    <soap:operation soapAction="getExistingAds" style="document" />    <wsdl:input>      <soap:body use="literal"                    namespace="http://daily-moon.com/classifieds" />    </wsdl:input>    <wsdl:output>      <soap:body use="literal"                    namespace="http://daily-moon.com/classifieds" />    </wsdl:output>  </wsdl:operation></wsdl:binding><wsdl:service name="ClassifiedService">  <wsdl:port name="ClassifiedServicePort"                             binding="tns:ClassifiedServiceBinding">    <soap:address location=         "http://127.0.0.1:8080/axis2/services/ClassifiedService" />  </wsdl:port></wsdl:service></wsdl:definitions>

其中的大部分更改都只是为了方便或提供易用性;“content”要比“param0”好记得多。其中的两个更改——顶部的命名空间和底部的命名空间前缀——是由于 Axis2 的 0.95 版中的两个小软件错误而需要进行的,在您阅读本文时,可能已经不再需要这样处理了。

回页首

从 WSDL 生成服务

有了 WSDL 文件后,Francis 就可以使用其生成服务和客户机。(实际上,Francis 可以直接使用 Larry 生成的版本。)

Francis 首先生成服务器端的代码,如清单 40 中所示:


清单 40. 服务器端代码
                    java org.apache.axis2.wsdl.WSDL2Java -uri ClassifiedService.wsdl -ss -sd -p org.dailymoon.classifieds -d xmlbeans -o service

(同样,请在一行输入此命令。)

第一个参数是 WSDL 文件的 URL。当然,您可以使用此应用程序访问远程文件。第二个开关 -ss 告知应用程序生成服务(而不是客户机)。-sd 开关告知应用程序生成 XML 服务描述符,从而更便于在生成服务代码后进行部署。下一个参数当然是包,其后紧跟数据绑定方法。可用的方法有 adbxmlbeans  jaxme。最后,为了保持条理清楚,Francis 将服务生成到名为 source 的新目录中。

所得到的结果是数百个文件。幸运的是,您只需要处理其中的一个即可。

回页首

实现服务

尽管在本例中,服务是从 WSDL 文件生成,而该文件本身又是从 Java 类生成的,但生成的代码中并没有实际的逻辑。其中仅包含相应的结构。为了让服务实际进行一些工作,需要对框架文件进行编辑。

在此结构中,要处理的文件如清单 41 中所示:


清单 41. 从 Java 类生成的文件
                    service\src\org\dailymoon\classifieds\ClassifiedServicePortTypeSkeleton.java

此代码中包含大量注释,当尝试自己进行配置时,这些注释就非常有用;但需要对其进行说明时,就很容易带来干扰。下面是经过清理的版本,并包含为了实现部分服务而添加的代码(请参见清单 42)。


清单 42. ClassifiedServicePortTypeSkeleton.java
                    package org.dailymoon.classifieds;public class ClassifiedServicePortTypeSkeleton {   public  axis2.apache.org.xsd.CreateNewAdResponseDocument                                                       createNewAd         (axis2.apache.org.xsd.CreateNewAdRequestDocument param0 )                                                  throws Exception {      //Todo fill this with the necessary business logic      //throw new  java.lang.UnsupportedOperationException();      System.out.println("New ad requested, to end on " +                           param0.getCreateNewAdRequest().getEndDate());      System.out.println(                  param0.getCreateNewAdRequest().getContent());      axis2.apache.org.xsd.CreateNewAdResponseDocument                                                responseDoc =          axis2.apache.org.xsd.CreateNewAdResponseDocument                                        .Factory.newInstance();      axis2.apache.org.xsd.CreateNewAdResponseDocument                               .CreateNewAdResponse response =                        responseDoc.addNewCreateNewAdResponse();      response.setNewAdId(1138);      return responseDoc;   }        public  void finalizeIssue           (axis2.apache.org.xsd.FinalizeIssueRequestDocument param2)                                                    throws Exception {        //Todo fill this with the necessary business logic                   }        public  axis2.apache.org.xsd.EditExistingAdResponseDocument                                                        editExistingAd        (axis2.apache.org.xsd.EditExistingAdRequestDocument param3)                                                     throws Exception {      //Todo fill this with the necessary business logic      throw new  java.lang.UnsupportedOperationException();   }        public  axis2.apache.org.xsd.GetExistingAdsResponseDocument                                                       getExistingAds        (axis2.apache.org.xsd.GetExistingAdsRequestDocument param5)                                                      throws Exception {      //Todo fill this with the necessary business logic      throw new  java.lang.UnsupportedOperationException();   }     }

在实际实现方法前,每个方法都会引发 UnsupportedOperationException。为了将数据提交到服务,请首先处理参数和获得请求本身。这样,就可以使用 getter 方法来提取各个成员了。

显然,在实际的服务中,您希望进行的不仅是输出文本,而 Francis 仅是为了确保此服务能够正常工作。要创建响应,请从恰当的响应文档着手,通过类的 Factory 获得一个实例。(类本身非常复杂,包含大量内部类,但有必要了解一下它的结构。)获得了此文档后,请创建实际响应本身,并将其添加到此文档中。

可以通过使用 setter 方法在响应上设置值。直接返回响应文档,支持类负责将其发送回请求方。

回页首

部署服务

为了部署服务,您需要对其进行编译,并将其转换为 Axis2 存档文件。首先编译和打包服务,如清单 43 中所示。


清单 43. 打包服务
                    set ANT_HOME=e:\apache-ant-1.6.5PATH=%PATH%;%ANT_HOME%\bin;set AXIS2_HOME=e:\axis2cd serviceant jar.service

对于非 Windows 安装,请对所用语法进行恰当调整,并确保使用的是实际文件位置。

此 Ant 任务将编译所有相应的文件,并创建两个存档文件 ClassifiedService.aar 和 XBeans-packaged.jar,这两个文件都位于 build/lib 目录中。

要部署服务,请确保 Geronimo 正在运行,并将浏览器指向清单 44 中所示的位置:


清单 44. 部署服务
http://localhost:8080/axis2/Login.jsp

使用凭据 admin/axis2 登录,并单击 Upload Service>Browse。导航到 ClassifiedService.aar 文件,然后单击 OK。单击 Upload,以完成此过程。

如果单击 View Services,应该看到列出了新服务。

回页首

从 WSDL 生成客户机存根

现在剩下任务就是生成用于访问新服务的客户机。为此,请从命令行执行以下命令:


清单 45. 用于生成客户机的命令
                    java org.apache.axis2.wsdl.WSDL2Java -uri ClassifiedService.wsdl -p org.dailymoon.classifieds -d xmlbeans -o client

同样,这也是单个命令,需要在单行中输入。相应的参数和服务器端代码生成几乎完全相同,不过不需要服务描述符。另外,为了保持条理性,Francis 将新文件放入了独立的 client 目录中。

这个类应该以静默方式执行,在调用时会生成数百个文件,但您并不需要直接处理其中的任何文件。

回页首

创建客户机

代码生成过程并不会实际创建客户机,但会创建一个类,可以利用此类方便地创建客户机。为了简化编译,Francis 在 client\src\org\dailymoon\classifieds 目录中创建了一个名为 Client.java 的新类文件。这样, Ant 就会选取 .java 文件,并将其与其他源文件一起编译。

Francis 添加了清单 46 中的代码。


清单 46. 客户机
                    package org.dailymoon.classifieds;import axis2.apache.org.xsd.*;public class Client{   public static void main(java.lang.String args[]){      try{         ClassifiedServicePortTypeStub stub =               new ClassifiedServicePortTypeStub(null,     "http://localhost:8080/axis2/services/ClassifiedService");                     CreateNewAdRequestDocument cnaDoc =                CreateNewAdRequestDocument.Factory.newInstance();         CreateNewAdRequestDocument.CreateNewAdRequest cnaReq =                               cnaDoc.addNewCreateNewAdRequest();         cnaReq.setContent("Vintage 1963 T-Bird...");         cnaReq.setEndDate("7/4/06");         CreateNewAdResponseDocument cnaResDoc =                                        stub.createNewAd(cnaDoc);         System.out.println("New ad ID number:  "+                cnaResDoc.getCreateNewAdResponse().getNewAdId());      } catch(Exception e){         e.printStackTrace();      }   }}

ClassifiedServicePortTypeStub 类表示实际的服务,您将使用 AXIS_HOME(此处进行了省略,以使用缺省设置)和实际服务的位置对其进行实例化。接下来,通过引用其 Factory 创建请求文档,并使用其创建新的 CreateNewAdRequest,而且在此过程中将其添加到请求文档中。和在服务本身中一样,可以随后直接使用 setter 方法设置各个属性。

为了获得响应,请使用存根执行 createNewAd() 方法,将其作为参数传递给请求文档。获得了响应文档或CreateNewAtResponseDocument 后,可以使用其提取响应本身以及该响应的属性。

现在让我们运行此应用程序。

回页首

运行客户机

为了运行客户机,Francis 首先需要对其进行编译。执行以下步骤(请参见清单 47)。


清单 47. 编译客户机
                    >>set ANT_HOME=e:\apache-ant-1.6.5>>PATH=%PATH%;%ANT_HOME%\bin;>>set AXIS2_HOME=e:\axis2>>cd client>>ant jar.clientBuildfile: build.xmlinit:pre.compile.test:     [echo] Stax Availability= true     [echo] Axis2 Availability= truecompile.src:compile.test:jar.client:BUILD SUCCESSFULTotal time: 2 seconds                

首先,确保环境中包含相应的环境变量。(这假定您已经安装了所有的 AXIS2_HOME\lib jar 文件。)接下来,转到 client 目录(或作为生成过程输出的任何目录)并针对 jar.client 目标运行 Ant。应该看到与斜体所示类似的结果(请参见清单 47)。要运行客户机,首先将 CLASSPATH 修改为包括 resources 目录以及包含数据绑定过程创建的所有类的目录(请参见清单 48)。


清单 48. 运行客户机
                    >>set CLASSPATH=E:\WSDLFiles\client\resources\;E:\WSDLFiles\client\build\classes\axis2\apache\org\xsd\;%CLASSPATH%>>cd build\classes>>java org.dailymoon.classifieds.Client 

应该看到与清单 49 所示类似的结果:


清单 49. New ad ID number
                    New ad ID number: 1138


本教程的内容到此结束了。
结束语
在本系列教程的第 1 部分中,Daily Moon 报社的 Classifieds Department 了解了如何使用 SOAP Web 服务连接到报社的内容管理系统。在本教程中,其员工了解了如何使用 Web 服务描述语言 (WSDL) 来描述自己的服务,以供其他人使用。本教程介绍了 XML 模式的基本知识,并说明了 WSDL 文件的结构,以便能够手动进行构建。本文还讨论了各种可用样式和编码间的差异。本教程还说明了如何使用随 Apache Axis2 提供的 Java2WSDL 和 WSDL2Java 工具,以便自动从 Java 文件生成 WSDL 或反向生成。
本系列教程的第 3 部分将讨论如何使用 UDDI 构建 Web 服务注册中心。