2002-08-27 10:39
无SOAP的Web服务,第一部分
--WSIF怎样领先于当前用于Web服务的客户机编程模型
Nirmal K. Mukhi(nmukhi@us.ibm.com)
副研究员,IBM Research
2001 年 9 月
即使 SOAP 只是众多访问 Web 服务的可能的绑定之一,它已几乎成为 Web 服务的同义词。这意味着使用 Web
服务的应用程序通常通过绑到 SOAP 的特定实现的 API 来完成工作。本系列文章将描述一个更通用的、独立于 SOAP 的调用 Web
服务的方法,称之为“Web 服务调用框架”(Web Service Invocation Framework(WSIF))。它专门设计来直接调用用“Web
服务描述语言”(Web Services Description Language(WSDL))描述的 Web 服务,隐藏了底层访问协议(比如
SOAP)的复杂性。
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 的抽象服务描述。
清单 1:股票报价 Web 服务的 WSDL 描述
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/">
足够简单:它描述了只带有由一个端口类型提供一个操作的服务。该操作等待一个字符串,被解释为股票的报价机符号,然后返回当前该股票报价,一个浮点值。为了实际使用该服务,您需要某个定义访问机制的绑定以及该绑定的服务端点。清单 2 展示了该服务的 SOAP 绑定:
清单 2:股票报价 Web 服务的 SOAP 绑定
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/">
http://www.ibm.com/namespace/wsif/samples/stockquote-interface"
location="stockquote-interface.wsdl"/>
encodingStyle="
http://schemas.xmlsoap.org/soap/encoding/"/>
这是一个标准的 SOAP 绑定,使用 RPC 风格和作为传输协议的 HTTP 与服务进行通信。在文档的 port 部分中,我定义了使用 SOAP 绑定可以访问服务的 URI。在清单 3 中,我将就通过 Apache SOAP 2.2 客户机端 API 和 WSIF 的客户机 API 使用该服务进行对比。
清单 3:用于服务访问的 Apache SOAP API 与 WSIF API 对比
--------------------------------------------------------------------------------
Apache
SOAP
API
--------------------------------------------------------------------------------
//
Step 1: identify the service
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
--------------------------------------------------------------------------------
//
Step 1: identify the service
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 访问股票报价服务
java
clients.DynamicInvoker http://services.xmethods.net/soap/urn:
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 所示。
清单 5:使用生成存根的 WSIF 访问股票报价服务。
StockquotePT service = new StockquotePTStub(WSIFUtils.readWSDL(null,
wsdlLocation),
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 服务以及怎样使运行时环境使用定制的探索性方法在多端口之间进行选择。
关于作者 |