级别: 中级 Russell Butek (butek@us.ibm.com), 开发人员, IBM
2003 年 10 月 01 日 在本技巧中,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)工作小组(任主席)、核心工作小组、以及互操作性工作小组。 |
对本文的评价
|