内容


使用 Web 服务调用框架

调用与协议无关的服务

Comments

介绍

既无须考虑提供 Web 服务的方法,也不要考虑该服务位于何处,Web 服务调用框架(Web Services Invocation Framework,WSIF)只是个用于调用该服务的 Java API,它很简单。它使开发者从必须针对具体的传输协议或服务环境的开发服务这一限制条件中解放出来。因此,它包含有能提供与绑定无关的访问任意 Web 服务的 API。根据运行时对服务的元数据进行研究的结果,它允许对 Web 服务执行无存根或全动态的调用。它还允许您能在运行时将某个绑定的已更新实现插入到 WSIF 中。如果您使用了 WSIF 的提供者的方案,它还能允许您将一个新的绑定在运行时插入到 WSIF 中。这就允许进行调用的服务将选择绑定的时间推迟到运行时。最后,由于它是严格按照 WSDL 的,因而任何可以用 WSDL 来描述的服务它都能调用。

WSIF 最初发表在 2001 年 10 月 的 alphaWorks 上(请参阅 参考资料)。自公布之后 alphaWorks 发行版已有超过 4000 份的下载记录。WSIF 的一位创造者写了两篇关于 WSIF 的优秀文章,描述了 WSIF 的动机及其使用,我们鼓励您去阅读它们(请到 参考资料参阅 Web service invocation sans SOAP,第 1 部分与第 2 部分)。

本文是对 WSIF 贡献给 Apache Software Foundation 的响应。WSIF 源代码已捐赠给了由 Axis 工程赞助的 Apache XML 项目。在 Apache CVS 树中得到,名为 xml-axis-wsif。在: http://cvs.apache.org/viewcvs.cgi/xml-axis-wsif您也可以浏览到该代码。

在本文中我会讲讲 WSIF 的动机、用法和体系结构。并概述了自 alphaWorks 公布以来对 WSIF 所进行的更改,还要看一下实验性的以及前瞻性的观点。但在开始所有工作之前,我要快速地概括说明一下 WSDL。

有关 WSDL 的一些背景

Web 服务描述语言(Web Services Description Language,请参阅 参考资料)本来就是可扩展的 ― 从一开始,设计人员就在 WSDL 中实现了服务接口与实现的分离。

在 WSDL 中,服务被定义为三个截然不同的部分:

  1. PortType。PortType 定义了由服务提供的抽象接口。一个 PortType 定义了一组 Operation。每一个 operation 可能为 In-Out(请求-响应)、In-Only、Out-Only 或 Out-In(恳求-响应)。每一个 operation 定义了 input 和/或 output Message。一个 message 又是被定义为一组 Part,而每一个 part 有一个由模式定义的类型。
  2. Binding。一个 binding 定义了如何在一个抽象 PortType 与一个真实的服务格式和协议之间建立映射关系。例如,SOAP 绑定定义了编码风格、SOAPAction 头和 body(targetURI)的名称空间等等。
  3. Port。Port 定义了可用服务的实际位置(端点)― 例如,SOAP 服务所在的 HTTP URL。

在现在的 WSDL 中,每一个 Port 有且只有一个 binding,而每一个 binding 有一个独一无二的 PortType。反过来(也是更重要的),每一个 Service(即 PortType)可能有多个 Port,而每一个 Port 则代表了一个作为替换的访问该服务的位置和绑定。

由于我们所需要的是基于 WSDL 而不是直接基于 SOAP 的 API,因而在设计 WSIF 时我们把 WSDL 作为参考对象。WSIF 实际上是一个插入对传输与格式支持的框架。在 WSIF 中它们叫 provider,而最明显的 provider 就是支持 SOAP 的 provider。

添加 可扩展性元素(extensibility elements)到 WSDL 中不仅允许创建 SOAP 描述,还能允许创建其他服务实现的描述。典型情况下,使用已存在的应用程序组件 ― 如 Java 类、EJB(Enterprise JavaBeans)或 COM 对象来实现 Web 服务。这些组件有时候就是旧系统上的应用程序 包装器,如大型机事务系统。通过扩展 WSDL 以便能描述已有的组件模型,我们能捕获到公开的可用 SOAP 服务和底层的组件之间的相关关系,以及两个可用实现实际上是相同的业务服务的事实。事实上,为现有的组件添加可扩展性元素会做更多的事情 ― 也给现有的组件模型增加了面向服务的体系结构的描述能力。

WSIF 的动机

WSIF 的动机是我们原来希望看到“面向服务的体系结构”(Services Oriented Architecture)比 SOAP 变得更广泛。有许多不同协议、传输和分布式计算技术比现在的 SOAP 提供的功能要多的多 ― 尤其在管理、事务、安全和其他服务质量(Quality of Service,OoS)特性方面上。正当 SOAP 迅速变得越来越受欢迎时,主要的问题实际上是投资之一了。许多已在象 CORBA 等技术上进行过投资的公司希望能继续使用和保持以前的技术。另一方面,带 SOAP 的 Web 服务有一个独一无二的优点 ― 用于描述和发现的基础结构。在 UDDI 目录中或检验文档中都能找到 WSDL 文件,任何人都能下载之,并可使用某个通用的可用工具来生成使用该服务的代码,而无须考虑是在本地网络中还是在因特网中。这种描述语言的通用可用性也正刺激着其他工具的增加 ― 例如,用于将服务编排组合在一起的语言(XLANG 和 WSFL)的开发。

我们真的想使 WSDL 的可扩展性与结构变成现实。WSDL 允许我们用可扩展性元素描述已存在系统。例如,我们已经写出了以下的 WSDL 扩展,能描述在使用连接器的 CICS 和 IMS 中的事务、能描述对远程无状态会话 Enterprise JavaBean 的调用、以及能描述基于 JMS 消息传递系统上的 SOAP 与非 SOAP 消息。可是,尽管在描述这些东西时,这些扩展很有用,但执行它们时却不那么有用了。

现在的 WSDL 不仅仅是一个描述层 ― 它还有一个在工具里的真正实现,在这些工具中可以使用 WSDL 描述来生成访问服务的存根。所以如果添加了非 SOAP 系统的描述,我们就处在失去该优点的危险之中。WSIF 解决了这个问题。事实上,作为一个可插入框架,WSIF 允许插入 provider。一个 provider是一段代码,该代码支持某一 WSDL 扩展,并允许根据具体的实现调用服务。这就意味着客户端代码是与该实现无关的,而仅仅取决于服务的 PortType。WSIF 也允许延迟绑定,此时一个新的提供者和描述在运行时是可用的,已有的客户端就能利用这个新的实现。最后,WSIF 还容许客户端把端口选择委托给基础结构和运行时,这样就容许在服务质量特点或业务策略的基础上来选择实现。

WSDL 的结构容许某一 Web 服务有多个实现和共享相同 PortType 的多个 Port。换句话说,WSDL 容许相同接口既可有 SOAP 绑定又可以有 IIOP 的绑定。我们想让我们的 API 能容许相同的客户端代码可访问任意可用的 binding ― 如果代码是写来用于 PortType 的,则它可能是一个使用哪个 port 和 binding 的部署或配置设置(或一个代码选择)。

我们为 WSIF 建立了下列必要条件。它必须是:

  1. 支持任意有效的 WSDL 扩展。
  2. 支持由 WSDL 描述的服务的动态调用。
  3. 支持基于 WSDL PortType 的 API。
  4. 容许延迟绑定到不同的格式与传输。
  5. 支持编译的方法与动态的方式。
  6. 支持最小代码部署说明(n 个提供者,无须每个服务都有代码)。
  7. 支持服务请求数据的不同的内存表示法。

WSIF 用法

有两种使用 WSIF 的方法:基于存根的模型和动态调用接口(Dynamic Invocation Interface,DII)。

存根模型

基于存根的模型容许用户使用常见的程序设计模型来调用 Web 服务的业务方法。有一个标准化的从 WSDL 定义的接口到 Java 定义的接口的映射 ― 在 JAX-RPC 标准中。有人试图通过 JCP/JSR 过程把 WSIF 模型与 JAX-RPC 集成起来,但感觉上 WSIF 太具有试验性了以至于不能在那时引入它。我们所做的工作是创建了一个松散集成。JAX-RPC 定义了服务定义接口(Service Definition Interface,SDI)― 存根的业务接口。WSIF 也定义了一个与 JAX-RPC 所使用的相同的 SDI 的接口。所以,虽然 WSIF 与 JAX-RPC 并不共享接口,但它们还是能共享工具(例如,Axis WSDL2Java)。 清单 1列出了存根模型的一个示例。

清单 1、WSIF 中使用存根模型的示例
1   WSIFService sq = ServiceFactory.newInstance().
    getService("http://my.com/svcs/stockquote.wsdl");
2   MyService mySvcStub = sq.getStub("soap", MyService.class);
3   mySvcStub.myMethod();

动态调用接口

DII 紧密地建模在 WSDL 基础上。在 WSDL 中,一个 operation 有一个 input message 以及一个可选的 output message 或 fault message。而在 WSIF 中,我们可看到一个类似的层次结构。二者之间有着密切的对应关系。事实上,我们的第一代就是一一对应的,但我们已重构了该对应关系以使之变得更合乎逻辑和(略微地)更简单。 表 1:WSDL 与 WSIF 实体对应关系列出了对应关系。

表 1:WSDL 与 WSIF 实体的对应关系
WSDLWSIF
(WSIL/UDDI)WSIFServiceFactory 或 JNDI
ServiceWSIFService
PortWSIFPort
Binding(无相应的实体)
OperationWSIFOperation
MessageWSIFMessage
Part(无相应的实体)

要使用 DII,您需要:

  1. 选择一个 port。
  2. 创建一个 operation。
  3. 创建并植入到 in-message。
  4. 执行该 operation。
  5. 从 out-message 中读出响应数据。

清单 2列出一个简单的示例。

清单 2、WSIF 中使用动态调用接口的例子
1   WSIFService sq = ServiceFactory.newInstance().
    getService("http://my.com/svcs/stockquote.wsdl");
2   WSIFPort defPort = sq.getPort();
3   WSIFOperation getQ = defPort.createOperation("getQuote");
4   WSIFMessage inMessage = getQ.createInputMessage();
5   inMessage.setStringPart("symbol", "IBM");
6   ...
7   getQ.executeRequestResponse(inMessage, outMsg, fltMsg);
8   outMessage.getFloatPart("value");

命令行工具 DynamicInvoker 是 WSIF alphaWorks 发行版的一个功能,它能调用接口内含有简单类型的任意 Web 服务。有时候,服务的参数使用了复杂类型,我们经常遇到的请求之一就是容许这些服务的动态调用。就是带着这个想法来设计 WSIF 的,但由于在第一代中我们只支持使用 JavaBean 组件来表示复杂类型,所以,若没有首先生成一个合适的 JavaBean 组件我们不可以调用需要这些复杂类型的服务。为补救该不足,我们已经开发出了一组叫 JROM 的类(最初时它仅代表 Java 记录对象模型(Java Record Object Model,JROM)― 但事实上它已经变成了一个专用名 ― 念作 Jay-Rom)。JROM 是一个抽象的树结构,它能在内存中表示大部分模式复杂类型。把 JROM 想象成一个轻量级的 DOM,树上的每片叶对象是一个基本类型(不象 DOM,每片叶对象都是字符串)。

即使在 classpath 中没有与模式复杂类型相匹配的 Java 类型,通过和 WSIF 一起使用 JROM,动态调用也是允许的。这是一个非常强大的方法,尤其表现在构建诸如网关、流程引擎或测试客户端这样的 Web 服务系统方面。还不是所有的提供者都支持 JROM,但我们已经在 ApacheSOAP 提供者中添加了对 JROM 的支持。

虽然不是 WSIF 开放源代码的一部分,但在 alphaWorks 处是可以下载到 JROM 的,请参阅 www.alphaworks.ibm.com/tech/jrom

WSIF 体系结构

WSIF 体系结构是建立在大量工厂上的。使用工厂是因为我们希望隐藏所使用的对象是否为“静态的”还是为“动态的”。当对象是静态时,它的生成是为了满足某个特定的 port、message 或 operation 的。这容许构建一个快速实现。而当对象是动态时,对象会在运行时使用 WSDL 描述来处理某特定的 port、operation 或 message 处理。目前,WSIF 主要使用动态对象,这也正是最初我们实现它们的方式。事实上我们设想了动态机制的几个不同层面。

全动态体系结构

象流程控制引擎这样的例子中,流程描述是作为一个 XML 文档而提供的,此时可能会用到该体系结构。在消息为创建一个新的聚集服务而被发送给其他操作之前,流程引擎会操作该消息。一个 provider 对应每个 binding 类型有一段部署代码。因而,如果存在着 b个不同 WSDL binding 扩展,就应该有 b个构件。

半动态体系结构

定义 PortType 的 message 是静态编译的,而 binding 却是动态的。这就不仅需要一个基于 PortType 的代码部署,还要加上每个 provider 对应于每 binding 类型的一个代码部署。所以,如果存在着 p个 PortType 和 b个的 WSDL binding,则就会有 p + b个构件。在接口发生改变时,已编译好的消息不得不被重新部署。

静态体系结构

WSDL binding 与 port 都是被静态编译的。在这种情况下,在处理时已定义的缺省 port 会被编译。每个实体对应每个绑定实例有一个代码部署。所以,构件的数目应该是 p x b

自 alphaWorks 发行以来的变化

自 alphaWorks 发行以来已有了许多的变化。鼓励您自己能看看开放源代码树。主要的变化是:

  • WSIFMessageWSIFPart模型的简化。我们删除了 WSIFPart接口而把之合并到 WSIFMessage中。为能捕获使用 WSIFMessage不同的样式,我们增加了一个叫 getRepresentationStyle()的方法。
  • PortFactory对象改名为 Service
  • 增加了 ServiceFactory接口。
  • 从使用 PortTypeCompiler到使用 J2SE 1.3 动态代理支持的改变。
  • 增加了新的绑定与传输 ― 基于 JMS 的 SOAP、EJB 绑定、基于 Apache Axis 的 SOAP 提供者。
  • 支持跟踪与日志记录活动。
  • 使用 J2SE JAR service provider 规范支持新 provider 的动态注册(请参阅 参考资料)。

对 WSIF 的试验性补充

目前,有很多要通过 WSIF 探索的领域,其中有异步请求-响应消息和上下文意识的消息。

异步请求-响应

目前我们有了一个异步请求-响应模型,响应在一个不同于原始请求的执行线程中被处理。为了支持它,基本上,请求者会注册一个回调对象或处理程序,在接收到响应时会调用之。

请求消息被送出时,处理程序对象也会被传送给 WSIFOperation。WSIF 提供了一个关联服务,它根据指定的标识符保存该处理程序。 WSIFOperation调用 WSIF 关联服务,它保存了服务本身和处理程序,该程序使用了请求的关联标识。这些都发生在当前事务的内部。如果请求用户指明了某个事务队列,那么会在当前事务内部完成这些工作。

WSIFOperationWSIFResponseHandler对象都是可序列化的,因而在请求与响应之间系统发生故障时,处理程序对象的状态可被保持。因为关联服务也是可插入的,所以为了高可用性,也就可以提供诸如原子事务和持久性等其他特性。

当响应来得有一些延迟时,侦听器线程会获得它并把它传递给已保存好的 WSIFOperation。操作包含有把响应数据编出到某一 WSIFMessage中的逻辑,然后会以 outMessage为参数,执行处理程序方法 executeAsyncResponse()。侦听器线程运行在与原始请求不一样的事务上的,因而响应也就是一个与请求不同的独立事务。

例如, 清单 3演示了在 WSIFResponseHandler 类基础上如何实现一个处理程序的。一个被设计成能理解响应消息和使用响应值的处理程序。

清单 3、在 WSIF 中实现异步处理程序
1   public class QuoteHandler implements WSIFResponseHandler  {
2          
3   protected String symbol = null; // local storage of the symbol we wanted quoted
4   public void setSymbol(String value) { symbol = value };   
5   public void executeAsyncResponse(WSIFMessage out, WSIFMessage fault) {
6            // this simplified example assumes no failures and that the symbol has 
            been set correctly
7       float quoteValue = out.getPart ("quote");
8       updateQuoteDB(symbol, quoteValue);
9   }

清单 4演示了服务调用。

清单 4、使用 WSIF 异步调用服务
1   String symbol = "IBM";
2   QuoteHandler quoteHandler = new QuoteHandler();
3   quoteHandler.setSymbol(symbol); // the handler needs to know 
    the symbol when it gets executed later.
4   
5   WSIFMessage inMessage;
6   WSIFOperation quoteOperation;
7   ...
8   inMessage.setXXXPart(...) // set up inMessage for invocation
9   quoteOperation.executeAsyncRequestResponse(inMessage, quoteHandler);

上下文意识消息

我们也希望能支持设置与使用上下文的想法。虽然在 W3C 的 WS-Description 工作组已有一些有关本主题的讨论,但还没有实际的语法或语义学。

例如,某个 SOAP/HTTP 端口可能要求一个 HTTP 用户名与密码。虽然该消息是特定于该调用的,但通常情况下却不是服务的参数。常规情况下,上下文是被定义为一组名称-值对。尽管如此,由于 Web 服务倾向于使用 XML Schema 类型来定义数据类型,所以我们选择使用一组已命名的 Part来表示上下文的名称-值对,每一个 Part 等于一个 XML Schema 类型的实例,这种表示方法与 WSIFMessages使用的表示方法是相同的。这比使用 属性概念稍微多一些开销,但却能更好地与 WSDL 和 WSIF 的其它特性相适合。

WSIPortWSIFOperation上的 setContext()getContext()方法允许应用程序设计人员或存根将上下文信息传递给 binding。Port 实现可能会使用该上下文 ― 例如来更新某个 SOAP 头。还没有关于 Port 如何使用上下文的定义。

未来的想法

我们希望看到在 WSIF 中有很多领域不断发展,来提升其功能性和有用性。首先,我们希望把传输从消息格式中分离出来的重构能力。我们定义了 WSIFFormatter类以允许访问数据以及数据到某个本地格式的转换。

接下来,一个消息内数据的抽象树表示可能是很用的。目前,我们主要是使用从模式编译出的类来携带 WSIFMessage中的服务参数。但在 WSIF 的某个实现中,即 Web Services Gateway(请参阅 参考资料)中,我们使用一个名为 JROM的抽象树概念,它密切地映射到某个 XML Schema。

我们还希望能看到更多更好的用于许多已有的传输类型与对象系统的提供者。最后,对用 C 或 C++ 创建服务的开发者来说,一个用 C 或用 C++ 编写的 WSIF 实现肯定是有帮助的。

总结

Web 服务调用框架是提供一个面向服务的框架的开放源代码倡议,它允许可在 WSDL 中描述和以通用方式调用 SOAP 和非 SOAP 服务。为了支持新的传输和协议,WSIF 定义了一个可插入接口(provider 接口)。通过支持代理接口和动态调用接口,WSIF 允许最终用户与系统软件开发人员使用 WSIF 提供的多种协议支持。WSIF 还支持延迟绑定,服务就可以被重新定位成新的协议类型而无须改变代码。WSIF 的开放源代码包括针对 Java、EJB 和 SOAP(基于 HTTP 与 JMS)的 provider。WSIF 已经被使用在 WebSphere 企业版 4.1 与 Versata 的 Logic Server WebServices Add-On 中。

致谢

按照字母顺序排列,对 WSIF 的发行作出贡献的人主要有:Aleksander A. Slominski、Anthony Elder、Dieter Koenig、Gerhard Pfau、Jeremy Hughes、Mark Whitlock、Matthew J. Duftler、Michael Beisiegel、Nirmal Mukhi、Owen Burroughs、Paul Fremantle、Piotr Przybylski 和 Sanjiva Weerawarana。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=22077
ArticleTitle=使用 Web 服务调用框架
publish-date=06012002