在本技巧中,IBM 开发人员
Russell Butek 向我们介绍了 JAX-RPC,这是一种 Java API,有了它,应用程序不需要理解
SOAP 消息传递协议的细节,就可以与 Web
服务通信。
Web
服务的基础之一是互操作性。意思是说 Web
服务相互之间用一种标准的格式发送和接收消息。典型的格式就是
SOAP。发送 SOAP 消息的方法有很多。
最基本的操作就是对一个
URL 流执行 println ,不过这要求您理解太多有关
SOAP 协议的知识,并且要知道某个服务期望收到的
SOAP
消息是什么样,发出的又是什么样。我们无法很方便地从服务本身获得这些信息,因此这种方法的可扩展性并不好。
再进一步就是使用 SAAJ API(SOAP
with Attachments API for Java),这种技术在 developerWorks
前一篇技巧
中介绍过了。SAAJ API
使您能够在稍微抽象一点的层次上操纵 SOAP
结构,但是依然存在一些对 URL 流执行 println
的问题。
一种更加友好的方法是使用一种系统,这种系统能够将应用程序推到一个比
SOAP
消息传递协议抽象得多的层次上。这种系统可以带来以下好处:
- 应用程序编程人员可以集中精力编写他们的应用逻辑,而不再被
SOAP 的严密逻辑困扰。
- 可以使用 SOAP
以外的其他消息传递模式,而应用程序代码只需做微小修改。
基于 XML 的 Java RPC API (Java
APIs for XML-based RPC,JAX-RPC)
正是这样一种抽象的方法。
JAX-RPC
以及 Barnes & Noble Web 服务
JAX-RPC 不依赖于 SOAP,而是依赖于 Web
服务描述语言(Web Services Description Language,WSDL)。WSDL
以声明性的标准化方式定义了访问 Web 服务的 API。WSDL
可以定义为将 SOAP
绑定到服务上,但是也可以在高于 SOAP
消息的层次上定义一种更加抽象的描述。(有关 WSDL
的更多信息,请参阅 参考资料)。
前面提到过一篇有关 SAAJ
的文章,其中的例子使用了位于 www.xmethods.net
的 Barnes & Noble Web 服务。XMethods
网站还提供了这个 Web 服务的 WSDL(请参阅 参考资料),请参见清单
1。本文的例子中使用到了这个 WSDL。
清单 1.
XMethods 上的 Barnes & Noble WSDL
<?xml version="1.0" ?>
<definitions name="BNQuoteService"
targetNamespace="http://www.xmethods.net/sd/BNQuoteService.wsdl"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://www.xmethods.net/sd/BNQuoteService.wsdl">
<message name="getPriceRequest">
<part name="isbn" type="xsd:string" />
</message>
<message name="getPriceResponse">
<part name="return" type="xsd:float" />
</message>
<portType name="BNQuotePortType">
<operation name="getPrice">
<input message="tns:getPriceRequest" name="getPrice" />
<output message="tns:getPriceResponse" name="getPriceResponse" />
</operation>
</portType>
<binding name="BNQuoteBinding" type="tns:BNQuotePortType">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http" />
<operation name="getPrice">
<soap:operation soapAction="" />
<input name="getPrice">
<soap:body
use="encoded"
namespace="urn:xmethods-BNPriceCheck"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</input>
<output name="getPriceResponse">
<soap:body
use="encoded"
namespace="urn:xmethods-BNPriceCheck"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</output>
</operation>
</binding>
<service name="BNQuoteService">
<documentation>Returns price of a book at BN.com given an
ISBN number</documentation>
<port name="BNQuotePort" binding="tns:BNQuoteBinding">
<soap:address location=
"http://services.xmethods.net:80/soap/servlet/rpcrouter" />
</port>
</service>
</definitions>
|
在任何 JAX-RPC
应用程序中,您都必须完成以下三件事情:
- 实例化一个
Service
类,这个类在客户端代表了一个 Web 服务。
- 在 Web
服务的应用程序中实例化一个代理(可能还要设置该代理)。
- 调用实现 Web
服务的应用程序中的操作。
JAX-RPC
DII 客户机应用程序
SAAJ API 并不依赖于 WSDL,它只依赖于 SOAP。但是您必须确切地知道这项服务需要的
SOAP 消息是什么样子。JAX-RPC
定义了一种动态调用接口(Dynamic Invocation Interface,DII)
API,用于访问 Web 服务,严格地讲,这些 Web
服务也不依赖于 WSDL(不过您还是必须了解从 WSDL
中获取的一些信息)。然而,您不再需要了解 SOAP
消息的详细内容。DII JAX-RPC
应用程序包括三个特定的步骤:
- 实例化一个没有 WSDL 的
DII
Service 类。
- 实例化一个 DII
Call 对象代理,并对其进行设置。
- 调用
Call
对象的 invoke 方法。
实例化一个没有
WSDL 的 DII Service 类
这个类不知道 WSDL,但是它必须有一个名字。因此我们使用
WSDL 中定义的服务名。
清单 2.
实例化 DII Service 类
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class DIITip {
public static void main(String args[]) {
try {
// Create a service class with no WSDL information. You
// still must know something about the WSDL, however: the
// service's name.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(serviceName);
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
|
实例化一个
DII Call 对象代理并对其进行设置
DII 代理对象实现 javax.xml.rpc.Call 接口。您可以从刚刚创建的那个服务中获得对
Call 接口的调用。
清单 3.
实例化 Call 对象
import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class DIITip {
public static void main(String args[]) {
try {
// Create a service class with no WSDL information. You
// still must know something about the WSDL, however: the
// service's name.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(serviceName);
// Now create a dynamic Call object from this service.
// This call object is not yet associated with any
// operation. We'll do that below.
Call call = service.createCall();
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
|
您现在拥有的这个 Call
对象还不可以用。它并不知道有关操作的任何事情。您需要将以下数据提供给这个对象:
- 操作名称:
getPrice
- 输入参数:一个字符串
- 返回值类型:一个浮点数
- 绑定信息:rpc
风格;编码风格
- 访问点:
http://services.xmethods.net:80/soap/servlet/rpcrouter
清单 4.
填充 Call 对象
import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.encoding.XMLType;
public class DIITip {
public static void main(String args[]) {
try {
// Create a service class with no WSDL information. You
// still must know something about the WSDL, however: the
// service's name.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(serviceName);
// Now create a dynamic Call object from this service.
// This call object is not yet associated with any
// operation. We'll do that below.
Call call = service.createCall();
// Next, build up the information about the operation...
// The operation name
QName operationName = new QName(
"urn:xmethods-BNPriceCheck",
"getPrice");
call.setOperationName(operationName);
// The input parameter
call.addParameter(
"isbn", // parameter name
XMLType.XSD_STRING, // parameter XML type QName
String.class, // parameter Java type class
ParameterMode.IN); // parameter mode
// The return
call.setReturnType(XMLType.XSD_FLOAT);
// The operation is an RPC-style operation.
call.setProperty(
Call.OPERATION_STYLE_PROPERTY,
"rpc");
// The encoding style property value comes from the
// binding's operation's input clauses encodingStyle
// attribute. Note that, in this case, this call is not
// really necessary - the value we're setting this
// property to is the default.
call.setProperty(
Call.ENCODINGSTYLE_URI_PROPERTY,
"http://schemas.xmlsoap.org/soap/encoding/");
// The target endpoint
call.setTargetEndpointAddress(
"http://services.xmethods.net:80/soap/servlet/rpcrouter");
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
|
调用
Call 对象的 invoke 方法
最后,您就可以调用 getPrice 操作了。
清单 5.
调用 getPrice 操作
import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.encoding.XMLType;
public class DIITip {
public static void main(String args[]) {
try {
// Create a service class with no WSDL information. You
// still must know something about the WSDL, however: the
// service's name.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(serviceName);
// Now create a dynamic Call object from this service.
// This call object is not yet associated with any
// operation. We'll do that below.
Call call = service.createCall();
// Next, build up the information about the operation...
// The operation name
QName operationName = new QName(
"urn:xmethods-BNPriceCheck",
"getPrice");
call.setOperationName(operationName);
// The input parameter
call.addParameter(
"isbn", // parameter name
XMLType.XSD_STRING, // parameter XML type QName
String.class, // parameter Java type class
ParameterMode.IN); // parameter mode
// The return
call.setReturnType(XMLType.XSD_FLOAT);
// The operation is an RPC-style operation.
call.setProperty(
Call.OPERATION_STYLE_PROPERTY,
"rpc");
// The encoding style property value comes from the
// binding's operation's input clauses encodingStyle
// attribute. Note that, in this case, this call is not
// really necessary - the value we're setting this
// property to is the default.
call.setProperty(
Call.ENCODINGSTYLE_URI_PROPERTY,
"http://schemas.xmlsoap.org/soap/encoding/");
// The target endpoint
call.setTargetEndpointAddress(
"http://services.xmethods.net:80/soap/servlet/rpcrouter");
// Invoke the operation
Object[] actualArgs = {"0672324229"};
Float price = (Float) call.invoke(actualArgs);
System.out.println("price = " + price);
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
|
然后您运行 java DIITip ,就得到价格:44.99 。
如果您将这段代码与 SAAJ
那篇文章中的进行比较,就可以看出,您的知识已经从
SOAP 协议向前迈进了一大步。您需要的信息中与 SOAP
有关的只有操作风格和编码风格。
正如其名字所暗示的,DII
编程模型是设计用于处理动态服务的。如果服务发生了微小改变,客户机的代码不需要修改太多就能与之相匹配,如果您真的很聪明的话,客户机代码也可以做成动态的,这样就根本不用修改了。
这种模型仍然是相当复杂的,尤其是您必须填充
Call 对象。操作越复杂,您需要提供给 Call
对象的信息也就越复杂。因此如果您知道服务是静态的,并且不会变化,那么您就可以在抽象的食物链上再上升一层。
JAX-RPC
SEI 客户机应用程序
接下来的一层抽象需要更多的设置工作,但是最终客户机的代码可就简单得多了。您可以从
WSDL 开始入手。JAX-RPC 实现提供了将 WSDL 转换成 Java
的代码生成器,它除了别的东西之外,还可以生成服务端点接口(Service
Endpoint Interface,SEI)。这是客户端访问 Web
服务应用程序的 Java 接口。清单 6 中显示了 XMethods
Barnes & Noble 应用程序的一个 SEI。
清单 6.
Barnes & Noble SEI
package xmethods.bn;
public interface BNQuotePortType extends java.rmi.Remote {
public float getPrice(java.lang.String isbn)
throws java.rmi.RemoteException;
}
|
在我所使用的 WSDL 转换成
Java 的工具中,我将 http://www.xmethods.net/sd/BNQuoteService.wsdl
的 targetNamespace 映射为 Java 包 xmethods.bn ,这样
SEI 就创建在这个包中。SEI 的名字派生于 WSDL 的 portType
名称:BNQuotePortType 。getPrice
方法派生于 portType 的 getPrice
操作以及它所引用的消息及其类型。如果您运行的是自己喜欢的
WSDL 转换成 Java 的工具,您将看到 xmethods.bn
包中还生成了其他的类。您必须将这些类一起编译,但是您的客户机应用程序只需要知道
SEI。
为了用这个 SEI 来调用 Web
服务,您必须完成下面三个与 DII 相同的步骤:
- 用 WSDL 实例化一个
Service
类
- 实例化一个代理
- 调用代理的操作
SEI 和 DII 的第 1步是类似的。第
2步和第 3步则大大简化,尤其是第 2步。
用
WSDL 实例化一个 Service类
SEI 模型和 DII 模型在这一步的唯一区别就是,在 SEI
中您需要多提供一些信息,即 WSDL。
清单 7. 为
SEI 实例化一个 Service 类
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
public class SEITip {
public static void main(String args[]) {
try {
// Create a service class with WSDL information.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
URL wsdlLocation = new URL
("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(
wsdlLocation,
serviceName);
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
|
实例化一个代理
一旦您获得了这个服务,就必须找到这个 SEI
的一个实现。这个实现就是用来访问真正应用程序的代理。所有有关如何访问那个应用程序的信息都隐藏在这个实现中,而这些信息是从
WSDL
的服务端口中搜集的,因此只要您获得了这个代理,您就不需要进行任何设置了;所有的工作都已经替您做好了。
清单 8.
实例化 SEI 的一个实现
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import xmethods.bn.BNQuotePortType;
public class SEITip {
public static void main(String args[]) {
try {
// Create a service class with WSDL information.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
URL wsdlLocation = new URL
("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(
wsdlLocation,
serviceName);
// Get an implementation for the SEI for the given port
QName portName = new QName("", "BNQuotePort");
BNQuotePortType quote = (BNQuotePortType) service.getPort(
portName,
BNQuotePortType.class);
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
|
调用代理的操作
最后,对操作的调用过程可能不太简单:
清单 9.
调用 SEI 上的一个操作
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import xmethods.bn.BNQuotePortType;
public class SEITip {
public static void main(String args[]) {
try {
// Create a service class with WSDL information.
QName serviceName = new QName(
"http://www.xmethods.net/sd/BNQuoteService.wsdl",
"BNQuoteService");
URL wsdlLocation = new URL
("http://www.xmethods.net/sd/2001/BNQuoteService.wsdl");
ServiceFactory factory = ServiceFactory.newInstance();
Service service = factory.createService(
wsdlLocation,
serviceName);
// Get an implementation for the SEI for the given port
QName portName = new QName("", "BNQuotePort");
BNQuotePortType quote = (BNQuotePortType) service.getPort(
portName,
BNQuotePortType.class);
// Invoke the operation
float price = quote.getPrice("0672324229");
System.out.println("price = " + price);
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
|
结束语
在调用 Web
服务的多种方法之中,最基本的方法是手工生成并发送
SOAP 消息。为此您必须掌握非常多的有关这项服务和
SOAP
协议的知识,因此这种方法不是非常有用。提高一步就是使用
SAAJ API。您仍然需要知道很多服务和 SOAP
协议的相关知识,所以这种方法也不是特别有用。从本文中再提高一步就是使用
JAX-RPC DII。这种方法使您摆脱了 SOAP,但是使用的代码还是很复杂。最好的方法就是根据服务的
WSDL 生成 SEI,并调用 SEI 代理。
参考资料
关于作者
Russell Butek 是 IBM WebSphere Web
服务引擎的一名开发人员。他还是 IBM 在
JAX-RPC Java Specification Request(JSR)专家组中的代表。他参与实现了
Apache 的 AXIS SOAP 引擎,并促使 AXIS 1.0 遵从
JAX-RPC 1.0规范。在此之前,他是 IBM CORBA ORB
开发人员和 IBM 在许多 OMG
工作小组的代表,包括:可移植拦截器(portable
interceptor)工作小组(任主席)、核心工作小组、以及互操作性工作小组。 |
|