2002-08-27 10:34
无SOAP的Web服务,第二部分
--“Web 服务调用框架”的体系结构,WSIF 的体系结构
Nirmal K. Mukhi(nmukhi@us.ibm.com),副研究员,IBM Research
Aleksandor Slominski(aslom@indiana.edu),研究助理,IU Extreme! Lab,Indiana University
2001 年 9 月
在前面的文章中介绍了 WSIF,说明了它怎样为简化的 Web 服务调用提供独立于绑定的 API。这篇文章将看看一些高级的
WSIF 功能。这将需要对其体系结构有一个概述,接下来您将看到怎样开发多服务绑定,以及怎样给 Web 服务更新和添加新的绑定实现。
“Web
服务调用框架”(Web Services Invocation Framework(WSIF))是为调用 Web 服务提供简单 API
的工具箱,而不管服务怎样提供或由哪里提供。在前面的文章中(请参阅参考资料),我们讨论了对 WSIF 的需要、其设计背后的思想以及描述了一些主要的功能。我们比较了
WSIF 的由 WSDL 驱动的 API 和用于使用Web 服务的常规 API,并且描述了端口类型编译器和无存根调用。
WSIF 甚至还可以提供更多。其体系结构允许通过端口工厂(port factory)发现调用端口。我们可以使用定制的绑定实现进行服务调用的调用端口,并且将它们插入到框架中。我们也可以设计我们自己的端口工厂,这样使用定制的算法能够查找或创建调用端口。最后,对于消息内用到的数据,WSIF 允许我们使用任何本机类型系统。在这篇文章中,我们将描述启用这些功能的 WSIF 的体系结构方面,并且讨论利用这种灵活体系结构的特定方法。
WSIF 的体系结构
WSIF 通过以下步骤调用服务操作:
装入 WSDL
文档。
为服务创建一个端口工厂。
该端口工厂用来检索服务端口。
如果需要,通过使用根据某个本机类型系统定型的消息部件创建消息。
通过给该端口提供待调用操作的名称以及操作需要的一个输入和/或输出消息进行调用。
WSIFPort
这里关键的抽象是 WSDL 端口的运行时表示,称之为
WSIFPort。它负责在特殊绑定的基础上进行实际调用。例如,为了调用抽象服务操作,我们要有 WSIFSOAPPort,它能使用为此服务在 WSDL 中指定的
SOAP 绑定。该体系结构的灵活性集中在清单 1 中所示的 WSIFPort 接口上。
清单 1:WSIFPort 接口
public interface
WSIFPort {
public boolean executeRequestResponseOperation (String
op,
WSIFMessage
input,
WSIFMessage
output,
WSIFMessage fault)
throws
WSIFException;
// some other methods not specified
here
}
这个接口的实现将必须知道使用指定的抽象输入和输出消息怎样调用操作。如果基于 Apache SOAP API,WSIFPort 的 SOAP 实现可以根据来自该服务 WSDL 文档的 SOAP 绑定信息创建一个 Call 对象,然后使用抽象输入消息创建调用必需的参数。可以用 SOAP 响应来植入随后可由客户机检查的抽象输出消息。
WSIFPortFactory
WSIFPortFactory
负责检索用于特殊调用的 WSIFPort。它具有清单 2 所描述的接口。
清单 2:WSIFPortFactory 接口
public
interface WSIFPortFactory {
public WSIFPort getPort () throws
WSIFException;
public WSIFPort getPort (String portName) throws
WSIFException;
}
请注意不带参数的 getPort 版本允许该接口实现有某个定制的算法来选择用于调用的 WSIFPort。WSIF 分发包有该接口的两个实现:静态端口工厂和动态端口工厂。静态端口工厂存储 WSIFPort 对象列表,并且当发出 getPort() 时,以伪随机方式返回其中一个对象。动态端口工厂存储动态提供者列表。这些提供者中的每一个能够根据服务的 WSDL 信息在运行时创建 WSIFPort。当在动态端口工厂调用 getPort() 时,工厂将以伪随机方式选择动态提供者,使它能创建接着返回的 WSIFPort。端口工厂体系结构的整体视图如图 1 所示。
图 1:WSIF 端口/端口工厂体系结构
WSIF 部件
到此为止我们讨论了 WSIF
端口怎样从特定的实现分离出来以及怎样用端口工厂来发现和创建端口。端口使我们能够建立调用,但是实施调用本身之前基本的一步是创建操作所需的消息。WSDL
中的消息由绑到某个类型系统的命名部件组成。典型情况下,将 XML 模式用作类型系统为 WSDL
部件定型。这独立于语言,非常强大。通过将这种模式类型映射到更方便的类型系统以及允许在属于该类型系统和模式类型的对象之间进行转换,从客户机调用这种服务。例如,当
Java 是本机类型系统时,使用设计为具有明确目的类,通过序列化和反序列化完成 Java 和模式之间的转换。
将 WSIF 的部件体系结构设计为任何本机类型系统可用于消息部件,并允许相同消息内的部件用不同的类型系统定型。在必须将来自两个或多个独立的 WSDL 消息(和不同的本机类型系统相关联)组合到单一消息中时,后者是必需的。除了允许有不同的本机类型系统,还需要有解释消息部件的通用的方法,这样就可能统一地查看 WSIF 消息对象。WSIF 采取的方法是模式为消息部件所用的标准抽象类型,并且所有这样的模式类型有相应的 JavaBean(可以使用将模式转换到 Java 的规范映射进行定义)。这给出了 WSIF 部件通用的类型系统。因此 WSIFPart 接口看似如清单 3 所示。
清单 3:WSIFPart 接口
public interface
WSIFPart {
public Class getJavaType ();
public Object
getJavaValue ();
}
只要能将这些表示转换为相应的规范 Java 类型,该接口的特定实现就可以使用任何一个内部类型系统。由 WSIF 提供的唯一 WSIFPart 接口实现是使用 Java 作为类型系统的 WSIFJavaPart。还有别的实现能够利用通用使用模式来提高效率。例如,请考虑在所用的最通用的绑定中与在文档风格 SOAP 绑定中一样,涉及 XML 文档交换。此处,将部件值表示为文字的 XML 而不是 Java 对象将非常高效,当使用文档风格 SOAP 绑定进行调用时,可以不需要解析或序列化/反序列化。
修改绑定选择算法
绑定选择算法的修改非常直截了当。所有开发者必须做的是编写他们自己的
WSIFPortFactory 实现,或扩展现有的实现。请考虑 WSIFDynamicPortFactory。它存储动态提供者列表,该列表根据需要给特殊的
WSDL 绑定生成一个 WSIFPort。例如,这个端口工厂根据提供者类型查找动态提供者,这样它知道哪一个动态提供者处理 SOAP
端口,哪一个处理我们可能定义的 CORBA 端口,等等。我们可以用我们自己的 getPort()
方法扩展该端口工厂来选择需要的端口。为了说明这是有帮助的情况,请考虑清单 4 中的地址簿 Web 服务。
清单 4:地址簿 Web 服务
xmlns:tns="http://www.ibm.com/namespace/wsif/samples/ab"
xmlns:typens="http://www.ibm.com/namespace/wsif/samples/ab/types"
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/">
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
http://www.ibm.com/namespace/wsif/samples/ab"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
http://www.ibm.com/namespace/wsif/samples/ab"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
该服务提供两种绑定:HTTP 之上的 SOAP 和本机 Java。在 WSDL 中直接绑定 Java 类没有标准的方法,但是这只会造成理解上的障碍,因此我们不再赘述。给定该服务多绑定的可用性,客户机将希望使用可能提供最好性能(比如 Java 绑定)的一个,大概因为它包含抽象操作到 Java 方法调用直接转换,而不需要网络、序列化/反序列化等操作。当然,Java 绑定只在部署了服务的环境可用。不象 SOAP 绑定,它不能在因特网上公用。因此我们希望做的是当可以时选择 Java 绑定,而在其它情况下使用 SOAP 绑定。如清单 5 所示,可以通过编写我们自己的 WSIFPortFactory 来实现。
清单 5:定制的绑定选择算法(请参阅参考资料以获取全部代码)
public
class MyWSIFPortFactory extends
WSIFDynamicPortFactory {
public WSIFPort
getPort () throws WSIFException {
WSIFException ex = null;
WSIFPort
portInstance = null;
// examine list of available ports
// do we have a
port with a Java binding?
for(int i = 0; i < myPortsArr.length; ++i)
{
try {
Port port = myPortsArr[i];
Binding binding =
port.getBinding();
if (binding instanceof JavaBinding) {
// attempt to
create a port that can
// make invocations using this binding
portInstance
=
createDynamicWSIFPort(def, service, port);
if(portInstance != null)
{
return portInstance;
}
}
} catch(WSIFException wex) {
if(ex ==
null) {
ex = wex;
}
}
}
// unable to create a port that can
invoke a Java
// binding (why? perhaps there is no Java port
// for this
service, or the Java port cannot be
// created for invocation since we are
not in
// an environment that has access to the resources
// that the Java
binding uses)
// use WSDL ports with other
// bindings for invoking the
service
// Full code not shown here due to length.
// Please see the
Resources section at the end of
// this article to download the actual code.
}
}
如果您下载了清单 5 中示例的全部代码(参考资料部分中可得到),您应该仔细查看下面的指导说明来执行它:
将 zip 压缩文档解压缩到某个目录(本例我们使用 C:\Forge)。它将创建一个子目录,比如
C:\Forget\wsif-1.0-my-factory。
当您准备好编译这个示例时,务必包括 WSIF 文档中描述的所有必需的 JAR
文件。
编译 C:\FORGE\wsif-1.0-my-factory\samples 中的样本源代码,然后将包含已编译的 Java .class
文件的这个目录添加到 CLASSPATH 环境变量中。
当它完全编译成功时,在命令行运行清单 6 中的第一个命令。它使用本地示例文件,并且利用修改的动态调用器。
清单 6:本地应用程序中的动态调用
C:\jdk1.3\bin\java.exe -classpath
".;C:\Forge\wsif-1.0-my-factory\samples;
C:\Forge\wsif-1.0\samples;C:\Forge\wsif-1.0\samples\generated;
C:\Forge\wsif-1.0\samples\generated;C:\Forge\xml-soap\xml_soap22.jar;C:\Forge
\jaxp-1.1\jaxp.jar;C:\Forge\jaxp-1.1\crimson.jar;C:\Forge\javamail-1.2\mail.jar;
C:\Forge\jaf-1.0.1\activation.jar;C:\Forge\wsif-1.0\lib\wsif-all-1.0.jar"
clients.MyDynamicInvoker samples\services\stockquote\Stockquote.wsdl
getQuote IBM
Reading WSDL document from
'samples\services\stockquote\Stockquote.wsdl'
Preparing WSIF dynamic
invocation
DEBUG: examining for selection port SOAPPort
DEBUG: selected
soap port SOAPPort
DEBUG: examining for selection port JavaPort
DEBUG:
selected java port JavaPort
Preffered port name 'JavaPort'
Executing
operation getQuote
Result:
quote=90.05
Done!
清单 7 展示了第二个使用 WSDL 的示例,来自 Web 服务站点 Xmethods.net 而不是本地示例文件。
清单 7:跨网络的动态调用
C:\jdk1.3\bin\java.exe
-classpath
".;C:\Forge\wsif-1.0-my-factory\samples;
C:\Forge\wsif-1.0\samples;C:\Forge\wsif-1.0\samples\generated;
C:\Forge\wsif-1.0\samples\generated;C:\Forge\xml-soap\xml_soap22.jar;
C:\Forge\jaxp-1.1\jaxp.jar;C:\Forge\jaxp-1.1\crimson.jar;
C:\Forge\javamail-1.2\mail.jar;C:\Forge\jaf-1.0.1\activation.jar;
C:\Forge\wsif-1.0\lib\wsif-all-1.0.jar"
clients.MyDynamicInvoker
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
DEBUG: examining for selection port
net.xmethods.services.stockquote.StockQuotePort
DEBUG: selected soap port
net.xmethods.services.stockquote.StockQuotePort
Preffered port name
'net.xmethods.services.stockquote.StockQuotePort'
Executing operation
getQuote
Result:
Result=90.0
Done!
在运行时更新/添加绑定实现
假如我们想要升级
SOAP 实现。使用该体系结构,我们可以不必重编译用户代码或存根代码完成到新的实现的迁移,因为 WSIF API 将保持不变。我们将编写新的 WSIFPort
实现,能够使用由新的 SOAP 实现提供的修改过的 SOAP 客户机 API 进行调用。接着,我们编写将 WSDL SOAP 接口转换到新的 WSIFPort
实现的动态提供者。一旦我们注册了这个新的动态提供者,它将代替以前注册的 SOAP 动态提供者,并且将通过新的 WSIFPort 实现进行所有 WSDL SOAP
端口调用。
请考虑一下我们定义我们自己的 WSDL 绑定的情况。首先,我们必须确保 WSIF 运行时能够装入带有新的绑定的 WSDL 文档;这需要我们为可扩展性元素定义处理程序,这些元素是使用 WSDL4J(请参阅参考资料)的扩展注册中心 API 添加到我们的 WSDL 中的元素。我们将不深入到这怎样实现的细节中。然后我们可以编写 WSIFPort 和动态提供者实现,以更新 SOAP 绑定同样的方式向端口工厂注册新的动态提供者。
存根体系结构
我们可以从这种体系结构得到的最重要的好处是:它使我们能够得到可由受管环境定制的客户机存根。所有
WSIF 存根扩展清单 8 指定的基本类。
清单 8:WSIFStub 基本类
// some methods and
other details omitted
public abstract class WSIFStub {
/**
* Locates a
port factory using JNDI if available,
* otherwise uses dynamic port factory
with
* pre-registered dynamic providers
*/
protected void
locatePortFactory(.....) {
.....
}
/**
* Initializes stub with ports
defined in WSDL document
* document must contain *exactly* one service and
all
* ports must implement one and only one port type.
*/
protected
void initializeFromLocation (...) {
.....
}
/**
* Return the port
currently being used.
*/
public WSIFPort getPort () {
return
wp;
}
/**
* Return the port factory currently being
used.
*/
public WSIFPortFactory getPortFactory () {
return
wpf;
}
/**
* Create a new proxy using the given
WSIFPortFactory.
*/
public void setPortFactory (WSIFPortFactory wpf)
{
.....
}
/**
* Create a new proxy pre-configured to use the given
port
* for interacting with the service.
*/
public void setPort
(WSIFPort wp) {
.....
}
/**
* Select the port to use by giving the
name of the port
* that is desired. If a port of that name cannot be retrived
from
* the port factory than an exception will be thrown. The port
* I use
will only be updated if this method is successful.
*/
public void
selectPort (String portName) {
.....
}
}
这种体系结构给了我们巨大的好处。在允许受管环境顺着模式作出改变的同时,它使应用程序能够使用同一存根,而不必重新编译任何东西。请考虑清单 5 中的例子,我们使用定制的绑定选择算法编写了我们自己的端口工厂。如果我们在应用程序服务器环境操作 JNDI,我们可以将该端口工厂实现绑定到恰当的指定的 JNDI 环境。当存根在初始化期间查找端口工厂时,它将得到新的实现。当然,我们可以通过直接调用存根上的 setPortFactory 方法强制在存根所使用的端口工厂进行改变(如果这是我们想要做的),或者我们可以通过使用存根的 setPort 方法强制使用特殊端口。
总结
随着 Web 服务持续发展,使用 SOAP
之外的协议访问服务端点将变得很普遍。因此当我们编写使用 Web 服务的应用程序时,我们需要工作在 WSDL 级而不是在 SOAP 级的
API。
WSIF 提供了着眼于 Web 服务调用的抽象方式,这样我们可以使用不受绑定束缚的调用 API 编写应用程序。WSIF 有生成可定制存根的端口类型编译器;WSIF API 足够简单,以至于应用程序能够通过直接使用它来进行无存根调用。WSIF 的体系结构能够以灵活的方式定义定制端口和端口工厂,并且也使被创建的消息能够使用任何本机类型系统。在存根用来进行调用的端口或端口工厂中,对于受管环境(比如应用程序服务器),生成的存根有入口点来进行运行时修改。
Web 服务需要独立于绑定的可扩展调用框架,WSIF 是朝该方向迈出的第一步。
关于作者 Nirmal K. Mukhi 是 IBM 的 T J Watson Research Lab 的“副研究员”,自 2000 年 11 月,他一直在从事各种 Web 服务技术的研究。他还对 AI、创造性编写以及过时的计算机游戏感兴趣。您可以通过 nmukhi@us.ibm.com 与 Nirmal 联系。 |
|