Web 服务承诺为因特网分布式计算提供基于标准的平台,集中在简易性和灵活性。一种关键的 Web 服务技术是 WSDL,即“Web 服务描述语言”。使用 WSDL,开发者可以以抽象的形式描述 Web 服务,与用在其它分布式计算框架(比如 CORBA)的现有“接口描述语言”(Interface Description Language(IDL))类似。WSDL 也使 Web 服务开发者能够给服务指定具体的绑定。并且这些绑定描述怎样将抽象服务描述映射到特定访问协议。WSDL 的这部分是可扩展的,这就是说任何人都可以提出他们自己的绑定,使之可能通过某个定制的协议访问服务。
因此,从某种程度来说,使用 Web 服务是一种挑战。对于同样的服务可以有多个绑定。这些绑定中的一些可能适合于一些情形,其它可能适合于另外的情形。绑定本身可以代表抽象服务描述到用来访问服务的任意一种协议的映射。虽然有多种选择使用服务和允许绑定扩展是很有用的,但这给客户机以统一方式查看服务造成了困难。
我以当前客户机端 API 及其性能的讨论开始这篇文章。我将通过讨论来激发人们对 WSIF,即“Web 服务调用框架”的需要,然后继续进行 WSIF 的概述。
当前的调用风格及其缺点
用于 Web 服务的 SOAP 绑定是 WSDL 规范的一部分。在大多数编程语言中,该协议有可用的实现和工具,在许多情况下是免费的。这样,它使得开发者能以微乎其微的成本进行用于 Web 服务的独立于平台的开发。
因此,下述情况是不足为奇的:大多数开发者当想到使用 Web 服务时,在他们头脑中出现的是使用某个 SOAP 客户机 API 来装配一个 SOAP 消息并将它经由网络发送到服务端点。例如,使用 Apache SOAP,客户机将创建和植入一个 Call 对象。它封装了服务端点、要调用的 SOAP 操作的标识、必须发送的参数等等。而这是对 SOAP 而言,它仅限于将其用作调用 Web 服务的一般模型,这是因为下面的原因:
- Web 服务不仅仅是 SOAP 服务
将 Web 服务视为 SOAP 上提供的服务的同义词。这是对 Web 服务狭隘的见解。带有功能方面和访问协议 WSDL 描述的任何一段代码均可以被认为是 Web 服务。WSDL 规范为 Web 服务定义了 SOAP 绑定,但是原则上可能要添加绑定扩展,这样,例如,使用 RMI/IIOP 作为访问协议,EJB 就可以作为 Web 服务来提供。或者您甚至可以想象任意一个 Java 类可以被当作 Web 服务,以本机 Java 调用作为访问协议。就这个更广阔的 Web 服务定义来说,您需要用于服务调用的独立于绑定的机制。 - 将客户机代码绑到一个特殊的协议实现要受到限制
将客户机代码紧密地绑定到特殊的协议实现的客户机库造成了难以维护的代码。让我们假设您在客户机端有一个用 Apache SOAP v2.1 调用 Web 服务的应用程序。如果您想利用 v2.2 中出现的新的功能和错误修正,您将不得不更新所有的客户机代码,这是一项耗时的任务,将涉及到常见的令人头痛的迁移问题。类似的,如果您想从 Apache SOAP 移到一个不同的 SOAP 实现,这个过程并非无关紧要。所需要的是用于服务调用的独立于协议实现的机制。 - 将新的绑定融入到客户机代码是很困难的。
WSDL 允许有定义新的绑定的可扩展性元素。这使开发者能够定义绑定,这种绑定允许使用某种定制协议的代码作为 Web 服务。但是,事实上实现起来是很困难的。将不得不设计使用该协议的客户机 API 。应用程序本身可能正是使用 Web 服务的抽象接口,因此将必须编写一些工具来生成启用抽象层的存根。这些又一次是并非无关紧要的而且耗时的任务。所需要的是使绑定能够被更新或新的绑定能够容易地插入的服务调用机制。 - 可以以灵活的方式使用多绑定
例如,设想您已经成功地部署了一个应用程序,该应用程序使用提供多绑定的 Web 服务。为了使这个示例更具体,假设您有用于服务的 SOAP 绑定和允许您将本地服务实现(一个 Java 类)作为 Web 服务的本地 Java 绑定。显而易见,如果客户机部署在与服务本身相同的环境中,只能使用面向服务的本地 Java 绑定,并且如果情况确实如此,通过直接进行 Java 调用而不是使用 SOAP 绑定与服务进行通信将高效得多。Java 绑定作为一种快捷访问机制。接下来,想要利用多绑定可用性的客户机将必须具有一种能力 — 根据运行时信息对要用的实际绑定进行切换的能力。因此为了利用提供多绑定的 Web 服务,您需要一种服务调用机制,允许您在运行时在可用的服务绑定之间进行切换,而不需要生成或重编译存根。
介绍 WSIF
“Web 服务调用框架”(WSIF)是为调用 Web 服务提供简单 API 的工具箱,而不管服务怎样提供或由哪里提供。它具有上面讨论中我确定的所有功能:
- 有给任何 Web 服务提供独立于绑定访问的 API。
- 提供端口类型编译器来生成允许使用抽象服务接口的调用的存根。
- 允许无存根(完全动态)的 Web 服务调用。
- 可以在运行时将更新的绑定实现插入到 WSIF。
- 可以在运行时插入的新的绑定。
- 允许将绑定选择延后到运行时。
分析 WSIF 的客户机 API
为了进行讨论,我将使用很常见的股票报价程序 — Web 服务的“Hello World”示例。请考虑下面清单 1 所示的使用 WSDL 的抽象服务描述。
<definitions targetNamespace="http://www.ibm.com/namespace/wsif/samples/stockquote-interface"
xmlns:tns="http://www.ibm.com/namespace/wsif/samples/stockquote-interface"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<message name="GetQuoteInput">
<part name="symbol" type="xsd:string"/>
</message>
<message name="GetQuoteOutput">
<part name="quote" type="xsd:float"/>
</message>
<portType name="StockquotePT">
<operation name="getQuote">
<input message="tns:GetQuoteInput"/>
<output message="tns:GetQuoteOutput"/>
</operation>
</portType>
</definitions>
足够简单:它描述了只带有由一个端口类型提供一个操作的服务。该操作等待一个字符串,被解释为股票的报价机符号,然后返回当前该股票报价,一个浮点值。为了实际使用该服务,您需要某个定义访问机制的绑定以及该绑定的服务端点。清单 2 展示了该服务的 SOAP 绑定:
<definitions targetNamespace="http://www.ibm.com/namespace/wsif/samples/stockquote"
xmlns:tns="http://www.ibm.com/namespace/wsif/samples/stockquote"
xmlns:tns-int="http://www.ibm.com/namespace/wsif/samples/stockquote-interface"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:java="http://schemas.xmlsoap.org/wsdl/java/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<import namespace="http://www.ibm.com/namespace/wsif/samples/stockquote-interface"
location="stockquote-interface.wsdl"/>
<binding name="SOAPBinding" type="tns-int:StockquotePT">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="getQuote">
<soap:operation soapAction="http://example.com/GetTradePrice"/>
<input>
<soap:body use="encoded"
namespace="urn:xmltoday-delayed-quotes"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
<output>
<soap:body use="encoded"
namespace="urn:xmltoday-delayed-quotes"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
</binding>
<service name="StockquoteService">
<documentation>Stock quote service</documentation>
<port name="SOAPPort" binding="tns:SOAPBinding">
<soap:address location="http://localhost:8080/soap/servlet/rpcrouter"/>
</port>
</service>
</definitions>
这是一个标准的 SOAP 绑定,使用 RPC 风格和作为传输协议的 HTTP 与服务进行通信。在文档的 port 部分中,我定义了使用 SOAP 绑定可以访问服务的 URI。在清单 3 中,我将就通过 Apache SOAP 2.2 客户机端 API 和 WSIF 的客户机 API 使用该服务进行对比。
清单 3:用于服务访问的 Apache SOAP API 与 WSIF API 对比
Call call = new Call();
call.setTargetObjectURI("urn:xmltoday-delayed-quotes");
// Step 2: identify the operation
call.setMethodName("getQuote");
call.setEncodingStyleURI(encodingStyleURI);
// Step 3: identify the parameters
Vector params = new Vector();
params.addElement(new Parameter("symbol",
String.class, symbol, null));
call.setParams(params);
// Step 4: execute the operation
Response resp = call.invoke(url,
"http://example.com/GetTradePrice");
// Step 5: extract the result or fault
if(resp.generatedFault())
{
Fault fault = resp.getFault();
System.out.println("Ouch, the call failed: ");
System.out.println(" Fault Code = " +
fault.getFaultCode());
System.out.println(" Fault String = " +
fault.getFaultString());
throw new SOAPException("Execution failed " + fault);
}
else
{
Parameter result = resp.getReturnValue();
return ((Float) result.getValue()).floatValue();
}
WSIF API
Definition def = WSIFUtils.readWSDL(null,wsdlLocation);
// Step 2: identify the operation (choose an
// appropriate service port)
WSIFDynamicPortFactory portFactory =
new WSIFDynamicPortFactory(def, null, null);
// Get default port
WSIFPort port = portFactory.getPort();
// The user can also explicitly select a port
// to use.
// WSIFPort port =
// portFactory.getPort("SOAPPort");
// Step 3: identify the parameters
// Prepare the input message
WSIFMessage input = port.createInputMessage();
input.setPart("symbol",
new WSIFJavaPart(String.class, symbol));
// Prepare a placeholder for the output value
WSIFMessage output = port.createOutputMessage();
// Step 4: execute the operation
port.executeRequestResponseOperation("getQuote",
input, output, null);
// Step 5: extract the result or fault
WSIFPart part = output.getPart("quote");
return ((Float)part.getJavaValue()).floatValue();
正如您可以看到的,WSIF 的 API 由以 WSDL 编写的抽象服务描述驱动;它完全从实际使用的绑定中分离出来。该调用 API 是面向 WSDL 的,并且使用它更自然,因为它使用 WSDL 术语引用消息部件(message part)、操作等等。当您阅读一个 WSDL 描述,出于直觉会想到选用支持所需端口类型的端口,然后通过提供必需抽象输入消息(由必要部件组成)调用操作(不用担心怎样将消息映射到特定的绑定协议);WSIF API 就是这样设计的。
常用的 API,比如上面所示的 Apache SOAP API 采用了以特殊协议为中心的概念,比如在使用 SOAP 的情况下,目标 URI 和编码风格。这是不可避免的,因为 API 不是普遍适用于 WSDL,而是为特殊的协议设计。
两种使用 WSIF 的调用模型
WSIF 允许 Web 服务以两种方式调用。一种是无存根的动态调用,它要求直接使用 WSIF API;另一种是通过生成允许应用程序使用 Java 接口(直接对应于 WSDL 端口类型)和隐藏了 WSIF API 的存根的调用。
无存根(动态的)调用
访问 Web 服务所需的所有信息 — 抽象接口、绑定和服务端点可以通过 WSDL 得到。如果您仔细查看上面的客户机 API 示例,您将会注意到唯一由用户提供的信息是用于服务的 WSDL 文件位置和所需要的股票报价符号。很明显接下来是用 WSFL API 在运行时装入这个服务和进行该调用。
WSIF 分发包包含了演示怎样执行 WSDL 的任意一个操作的 DynamicInvoker。它以 WSDL 文件的 URI 和所需的操作参数作为命令行参数(在最初的实现中只接受简单的类型,如字符串),并且直接使用 WSIF API 完成工作。其用来调用股票报价服务的示例如清单 4 所示;您可以在包含 WSIF 分发包的文档中查找详细信息。
因为这类调用不会导致生成存根类,并且不需要单独的编译周期,所以它很方便。
清单 4:使用 DynamicInvoker 访问股票报价服务
xmethods-delayed-quotes.wsdl getQuote IBM
Reading WSDL document from 'http://services.xmethods.net/soap/urn:
xmethods-delayed-quotes.wsdl'
Preparing WSIF dynamic invocation
Executing operation getQuote
Result:
Result=108.8
Done!
使用存根的调用
在应用程序需要更抽象的服务视图和不希望处理 WSIF 客户机 API 的情况下,无存根调用不适用。WSIF 提供一个端口编译器,如果给定一个 WSDL,与所用到的复杂类型的 Java 等价物(以 JavaBean 的形式)一起,端口编译器将为每一个端口类型生成一个客户机存根。使用生成存根的应用程序可以利用抽象服务接口,并且这样与协议和 WSIF 客户机 API 分离。对于实际服务调用,这些存根使用缺省端口。通过由 WSIF 的端口类型编译器生成的存根使用股票报价服务的示例如清单 5 所示。
null, null);
float quote = service.getQuote("IBM");
System.err.println (">> Received quote: "+quote);
值得注意的是存根驻留在某些受管环境(如应用程序服务器)的情况,它们能够被定制;例如,可以改变负责返回恰当端口的工厂来定制端口选择算法,或者可以改变用于调用自身的实际端口。
在这篇文章中,我略述了对 WSIF 的需要,分析其通过高级 API 进行独立于绑定的服务访问的主要特征以及生成可定制的存根(通过其抽象接口使用服务)的端口类型编译器的可用性。直接通过 SOAP 客户机 API 和使用 WSIF API 进行服务访问之间的对比展示了 WSIF(由服务的 WSDL 描述驱动)怎样将 Web 服务调用的全部问题从以绑定为中心的观点转移到更抽象的级别。这是 WSIF 的设计原则之一,使服务绑定能被看作是根据特殊协议进行调用所需的代码片断,可以在任何时候插入它们。
在接下来的文章,我将分析 WSIF 的体系结构,看一看它怎样允许新的或更新的绑定实现被插入,它怎样使定制的类型系统用于 Web 服务以及怎样使运行时环境使用定制的探索性方法在多端口之间进行选择。
- 请参加关于这篇文章的讨论论坛。
- 请下载 alphaworks 上的 WSIF 分发包,并且试验比较简单的样本。它将给您一个由 WSIF 支持的不同调用风格的第一手示例以及它优于特定协议客户机 API 的优势。
- 重温 WSDL 规范,看一看允许哪一种扩展;如果用来为访问 Web 服务定义 SOAP 绑定,您也可以学习 WSDL 的扩展机制。
- 重温 SOAP 规范本身。
- 如果以前您没有对 Web 服务编过程,Web Services Toolkit 是很好的起点。
- 请查看 WSDL4J,一个可扩展的 WSDL 分析框架,WSIF 在此基础上进行构建。
关于作者
Nirmal K. Mukhi 是 IBM 的 T J Watson Research Lab 的副研究员,自 2000 年 11 月,他一直在从事各种 Web 服务技术的研究。他还对 AI、创造性写作以及过时的计算机游戏感兴趣。您可以通过 nmukhi@us.ibm.com 与 Nirmal 联系。
转载自:http://www.itepub.net/page/article/htmldata/2004_10/17/76/article_3575_1.html