用于 Web 服务的 SOAP 绑定是 WSDL 规范的一部分。在大多数编程语言中,该协议有可用的实现和工具,在许多情况下是免费的。这样,它使得开发者能以微乎其微的成本进行用于 Web 服务的独立于平台的开发。因此,下述情况是不足为奇的:大多数开发者当想到使用 Web 服务时,在他们头脑中出现的是使用某个 SOAP 客户机 API 来装配一个 SOAP 消息并将它经由网络发送到服务端点。例如,使用 Apache SOAP,客户机将创建和植入一个 Call 对象。它封装了服务端点、要调用的 SOAP 操作的标识、必须发送的参数等等。
所以本文先探讨当前客户端使用Web服务的几种方式,说说目前对于web服务的调用都有哪些常用的解决方案。
u Web服务的调用过程
整个过程肯定有两方参与:服务请求端,即客户端及服务提供商,即服务端。
客户端:取得服务端的服务描述文件WSDL,解析该文件的内容,了解服务端的服务信息,以及调用方式。根据需要,生成恰当的SOAP请求消息(指定调用的方法,已经调用的参数),发往服务端。等待服务端返回的SOAP回应消息,解析得到的返回值。
服务端:生成服务描述文件,以供客户端获取。接收客户端发来的SOAP请求消息,解析其中的方法调用和参数格式。根据WSDL描述,调用相应的后台对象,在Java语言里如POJO或者是重量级的EJB等来完成指定功能,并把返回值放入SOAP回应消息返回给用户。
u 客户端调用Web服务的方式
Ø 高层接口
使用高层接口,不需要知道SOAP和XML的任何信息,就可以生成和使用一个Web Service,无论是基于Java平台还是.Net平台,我们可以使用一个叫做Proxy,即代理类。Proxy的作用是充当消费者应用程序或Web页面和生产者,即Web服务之间的中间过程。对生产者Web服务的每一个方法来说,同时也在Proxy类里有一个相同的方法,Proxy的职责就是处理所有传送的复杂消息,这种复杂性在Proxy类里被隐藏起来的,我们只需要简单地调用该类的方法即可,不必关心语义的事情。在Java平台,按这种高层接口实现的传统方式法有:
l 静态调用(Static Proxy)
也就是构建占位程序(代理,桩),为了理解这种高层接口的工作方式,就拿JAX-RPC来作例子,若想要了解另外一种:JAX_WS的工作方式,可以参考我那篇blog: 快速实践JAX-WS 2.0: http://blog.csdn.net/lin_bei/archive/2006/11/07/1371131.aspx。
虽然说JAX_RPC建立在复杂的协议之上,但API为应用程序开发者隐藏了这一复杂性。在服务器端,开发人员通过定义Java编程语言中接口的方法来指定远程过程。开发者还将编写一个或多个实现这些方法的类。客户端程序也是容易编写的。一个客户生成一个代理,表示服务的本地对象,然后简单的调用代理上的方法。这种方法在实现上通过IDE或命令行调用WSDL与JAVA之间的映射来构建,生成客户端代理,此时,调用客户端的代理类即可调用WEB服务。
优点:实现WEB服务应用比较简单,是传统的WEB服务应用最常用的方式之一。
缺点:不能实现动态调用,需要先通过映射工具构建客户端,生成代理,才能利用客户端来调用远程服务。
l 动态调用(Dynamic Proxy)
服务 的 调 用,一般可分为静态和动态两类。静态调用就是根据WSDL文件生成Java(WSDL2Java)或者.NET(wsdl.exe)的客户端代理,然后以stub-skeleton的方式调用Web服务,这种调用需要手工的干预,具体地过程就像1静态调用所说的。
之所以叫做动态调用,是因为它不需要先构建客户端代理,但调用过程中需要知道远程服务的细节。传统的动态调用一般采用RPC方法,如Apache Soap,Axis,但这种调用方式往往有很大的局限,调用过程中需要知道远程Web服务的更多细节。
Axis这种客户端的调用(Dynamic Proxy方式访问服务)如下所示:
……
String wsdlUrl = "http://localhost:8080/Axis/HelloClient.jws?wsdl";
String nameSpaceUri = "http://localhost:8080/Axis/HelloClient.jws";
String serviceName = "HelloClientService";
String portName = "HelloClient";
ServiceFactory serviceFactory = ServiceFactory.newInstance();
Service afService
=serviceFactory.createService(new URL(wsdlUrl),new QName(nameSpaceUri, serviceName));
HelloClientInterface proxy =
(HelloClientInterface)afService.getPort(new QName(nameSpaceUri, portName),
HelloClientInterface.class);
……
优点:不必构建占位程序,实现了一定的动态性。
缺点:与远程服务的关联太紧密,耦合度太高。
Ø 低层接口
要使用低层接口,你必须对SOAP和XML有所了解。你可以对SOAP的处理过程进行控制,特别是要做特殊处理的时候。客户端,首先要创建一个HttpConnector对象,负责HTTP连接。设定Connector的一些头部信息,比如EndPoinURL和SoapAction等。如果网络连接需要使用代理服务器,那也要在这里设定相关的信息。接着创建SoapSerializer对象,用于生成Soap消息。按照WSDL里定义,把所有参数按顺序序列化,得到一个完整的SOAP请求消息。该Soap消息,作为Payload通过HttpConnector被发送到服务端。最后,生成一个SoapReader对象,负责读取服务端返回的SOAP消息,取得其中的返回值。实现上这种使用低层接口比较多的就是动态调用接口。
l 动态调用接口(DII,Dynamic Invocation Interface)
首先它也是属于Web服务动态调用,DII 紧密地建模在 WSDL 基础上。在 WSDL 中,一个 operation 有一个 input message 以及一个可选的 output message 或 fault message。而在 我们最终的实现系统Cactus搜索与执行引擎中,我们可看到一个类似的层次结构。二者之间有着密切的对应关系。
表 1:WSDL 与 Cactus搜索与执行引擎实体的对应关系
WSDL | Cactus 引擎 |
Service | com.swc.se.domain.Service |
Operation | com.swc.se.domain.Operation |
Message | com.swc.se.domain.Parameter |
Binding | (无相应的实体) |
Part | (无相应的实体) …… |
要使用 DII,您需要:
1) 选择一个 port。
2) 创建一个 operation。
3) 创建并植入到 in-message。
4) 执行该 operation。
5) 从 out-message 中读出响应数据。
Axis这种客户端的调用(DII(Dynamic Invocation Interface )方式访问服务)如下所示:
String endpoint = "http://localhost:8080/Axis/HelloClient.jws";
Service service = new Service();
Call call = null;
call = (Call) service.createCall();
call.setOperationName(new QName("http://localhost:8080/Axis/HelloClient.jws",
"getName"));
call.setTargetEndpointAddress (new java.net.URL(endpoint));
String ret = (String) call.invoke(new Object[] {"zhangsan"});
System.out.println("return value is " + ret);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
以上是WEB服务供应商支持的最常用的三种构建客户端方式。但它们公用的缺点:耦合度太高,您可以在具体某一个应用整合过程中用得很好。但是,以上调用过程只能在调用方对于远程服务从人为的角度特别了解的情况下才能实现,不能实现我们想要的功能。
现在再想想最开始时提出的项目需求:只知道异构平台服务的WSDL位置,但需要动态匹配用户输入数据和SOAP消息的有效负载,并构建SOAP消息发送到客户端,这样我们是并不了解Web服务端。传统的客户端构建方式(JAX-RPC),以及Axis的动态调用接口的解决方案很接近这种需求,但是难以实现以上功能,因为它对于复杂类型参数的支持不是很好,所以我们肯定是得使用低层接口的解决方案,但是不是借助Axis这种现成的SOAP处理工具,而是选用SAAJ——用SOAP协议实现基于XML消息传递。
我将在下一篇blog里讨论我们基于SAAJ的解决方案。