用Java客户机调用Web服务:J2SE和J2EE环境中Web服务客户机简介

Web 服务的力量在于互操作性。由于业界在 Web 服务技术方面(SOAP、WSDL、UDDI)的协作,更具体地说,是由于 Web 服务互操作性组织(Web Services Interoperability organization,WS-I.org)的工作,Web 服务才可以与其他的 Web 服务进行交互,而不管 Web 服务开发和运行在哪一个平台上(比如是 Microsoft .NET 还是 IBM WebSphere)。Web 服务客户机分为多种类型,比如另一个 Web 服务、用脚本语言编写的客户机、C# 客户机、Java 客户机等等。本文重点讲解 Java 客户机,它可以用于访问任何遵循 Web 服务规范的 Web 服务(不仅仅是 Java Web 服务)。通过阅读本文,您将了解到需要用来通过不同的查找和访问方法调用相同的 Web 服务的 Web 服务客户机代码。本文所用的示例是“Hello” Web 服务,它提供了“getGreeting”操作。这种操作接受一个字符串参数(例如Jane),然后返回一句问候语“Hello Jane!”。

Web 服务角色

这一部分将描述调用 Web 服务的过程。Web 服务提供者用 Web 服务描述语言(Web Services Description Language,WSDL)文档来描述 Web 服务。Web 服务一般发布到统一描述、发现和集成(Universal Description, Discovery and Integration,UDDI)注册中心。Web 服务请求者在 UDDI 注册中心查找 Web 服务,绑定到 Web 服务,然后调用它。Web 服务角色显示在 图1中。本文将重点讲解从服务请求者到服务提供者的水平箭头(绑定)。本文将把请求者称为 客户机,它也可以称为 消费者。

用于

Java 技术标准的开发随着 Java 规范提案(Java Specification Request,JSR)提交给Java Community Process(JCP)而出现。两个 JSR 涵盖了 Java Web 服务体系结构:

JSR 101: 用于基于 XML 的 RPC 的 Java API(Java API for XML based RPC,JAX-RPC)

JSR 109: 实现企业 Web 服务(Implementing Enterprise Web services)

两个规范提供了厂商的实现的一致性和互操作性需求。

JAX-RPC —— Java 到 XML 和 XML 到 Java 映射 API

JAX-RPC 为基于 XML 的远程过程调用(Remote Procedure Call,RPC)和 Java 应用程序编程接口(Java Application Programming Interface,API):

WSDL 到 Java 和 Java 到 WSDL 映射:例如,将 WSDL 端口类型映射到 Java 服务端点接口(Java Service Endpoint Interface,SEI)。

XML 数据类型到 Java 数据类型和 Java 数据类型到 XML 数据类型映射,包括简单类型、复杂类型和数组。

除了 XML 映射之外,JAX-RPC 还定义了服务器端编程模型和 API,我将在后面的部分中更详细地介绍它。AX-RPC 1.1 根据 Web 服务互操作性组织(Web Services Interoperability organization,WS-I)基本概要版本 1.0(Basic Profile version 1.0)添加了互操作性需求。

JSR 109 —— J2EE 环境的 API

JSR 109 指定了 Java 2 Enterprise Edition(J2EE)环境的 Web 服务编程模型和体系结构。JSR 109 构建在 SOAP 1.1 和 WSDL 1.1 的基础上,它涵盖了 J2EE 环境中 JAX-RPC 的使用( 图 2)。它还定义了 J2EE 应用程序服务器中的部署模型。JSR 109 的客户端编程模型(我将在下面的几个部分中介绍)符合 JAX-RPC。

JAX-RPC 1.1 和 JSR 109 是 J2EE 1.4 的组成部分。

服务查找

有两种客户机,它们在代码的编写、打包和调用的方式上都不相同:

非受管客户机

J2EE 容器管理的客户机

这里,非受管意指不是 J2EE 容器管理的。这些客户机是 Java 2 Standard Edition(J2SE)客户机,它们是通过简单的 java 命令进行调用的。对于非受管客户机,服务查找是通过 JAX-RPC ServiceFactory 进行的,JAX-RPC ServiceFactory 是创建服务访问点的工厂。对于 J2EE 容器管理的客户机,服务查找是通过 JNDI 查找进行的。

ServiceFactoryJSR 101:“JAX-RPC ServiceFactory 是在 J2SE 环境中查找 Web 服务的标准方式。”

JAX-RPC ServiceFactory

JAX-RPC ServiceFactory 是一个抽象类,用作实例化 JAX-RPC Service 的工厂。它是厂商无关的,使您能够编写可移植代码。ServiceFactory 是实例化的,可以如下进行使用: javax.xml.rpc.Service service = ServiceFactory.newInstance().createService(…);

您需要将 Web 服务的全限定名(也就是名称空间加上服务名称)传送到 createService() 方法和(可选)描述您想要查找的 Web 服务的 WSDL URL。步骤如下:

(可选)指定 WSDL URL。

指定 Web 服务的全限定名。

调用 ServiceFactory 的 createService() 方法。

然后可以使用获得的服务接口(Service Interface)来获取存根、动态代理、或 DII Call 对象,如“ 服务访问”部分所述。在该部分中,同时还描述了动态调用接口(Dynamic Invocation Interface,DII)。使用这种方法,您不需要知道您想要调用的 Web 服务的 WSDL URL,您只需要指定 createService() 方法中的服务名称参数。清单1展示了如何使用 ServiceFactory 实现 JAX-RPC Service 。QName 是一个 javax.xml.namespace.QName 。

清单 1. 使用 JAX-RPC ServiceFactory 获得 JAX-RPC 服务

String wsdlURL = http://localhost:6080/HelloWebService/services/Hello?wsdl";String namespace = "http://Hello.com";String serviceName = "HelloWebService";QName serviceQN = new QName(namespace, serviceName);ServiceFactory serviceFactory = ServiceFactory.newInstance();/* The "new URL(wsdlURL)" parameter is optional */Service service = serviceFactory.createService(new URL(wsdlURL), serviceQN);

由特定于厂商的 JAX-RPC ServiceFactory 可供选择。如果您想要利用某个厂商的存根,使用这些 JAX-RPC ServiceFactory 通常是非常容易的(客户机代码编写起来很简单)。然而,这样的扩展不是标准的,将很可能在其他厂商的 J2EE 实现上不起作用。

JNDI 查找

JSR 109:“JNDI 查找是在 J2EE 环境中查找 Web 服务的标准方式。”

JNDI 服务查找

J2EE 容器管理的客户机被打包成 Enterprise Archives(.EAR)文件,并且在 J2EE 容器中运行。除了 Java 代码之外,描述符也打包在该归档文件中。下面是几个不同类型的 J2EE 容器管理的客户机:

应用程序客户机容器客户机

Web 容器客户机:JavaBean 或 Servlet

EJB 容器客户机:EJB

JAX-RPC 定义了受管客户机的编程模型,而 JSR 109(“实现企业 Web 服务(Implementing Enterprise Web services)”)定义了 J2EE 容器受管的客户机的编程模型。JSR 109 的目标之一就是它的客户机编程模型遵循 JAX-RPC。然而,JSR 109 并没有推荐使用 JAX-RPC ServiceFactory 。相反,它建议客户机使用 Java 命名和目录接口(Java Naming and Directory Interface,JNDI)来获取服务接口(Service Interface)。这个过程包括下面两个步骤,同时在 清单2中进行了举例说明:

实例化本地 JNDI 上下文。

在此上下文中对 Web 服务进行 JNDI 查找。

清单 2. JNDI 服务查找

Context ic = new InitialContext();Service service = (Service) ctx.lookup("java:comp/env/service/HelloService");

Web 服务的名称(在本例中为 java:comp/env/service/HelloService )是在客户机应用程序的部署描述符中指定的。JSR 109 建议把服务引用的所有逻辑名组织在 service 子目录中。如果客户机环境上下文是 java:comp/env ,您就可以以下面的代码结束:

service name in context =client environment context + "service" subcontext + service name.

在本例中,上下文中的服务名为:

java:comp/env/ + service/ + HelloService.

service 子上下文(subcontext) + 服务名(例如 service/HelloService )也称为逻辑上的服务名,是在 Web 服务客户机应用程序的部署描述符中进行声明的。

JNDI 查找返回 JAX-RPC Service Interface。J2EE 容器确保在部署描述符中指定的绑定通用 JAX-RPC Service 的实现。您也可以将该查找返回的对象强制转换成您的 Web 服务的指定接口。这示于 清单3,其中 HelloService 扩展了通用的 JAX-RPC Service 接口。

清单 3. 可供选择的 JNDI 查找

Context ic= new InitialContext();HelloServiceInterface service =   (HelloServiceInterface) ic.lookup("java:comp/env/service/HelloService");

然后可以使用获得的服务接口(Service Interface)来获取静态存根、动态代理或 DII Call 对象,如下面的“ 服务访问”部分所述。

服务访问

在前面的部分中,您看到了 JAX-RPC ServiceFactory 用作 JAX-RPC Services 的工厂。同样地,JAX-RPC Service 也用作代理和存根的工厂。一旦您实例化了服务,就拥有了三种访问和调用 Web 服务的方法:

存根

动态代理

动态调用接口(Dynamic Invocation Interface,DII)

存根和动态代理方法使用服务端点接口(Service Endpoint Interface,SEI)。它基本上是 WSDL 端口类型元素中描述 Web 服务操作的 Java 表示。它是定义 Java 客户机用来与 Web 服务进行交互的方法的 Java 接口。SEI 是由从 WSDL 到 Java 的映射工具(比如 Apache Axis 的 Java2WSDL 或 IBM WSDK 的 WSDL2Client)生成的。

SEI

服务端点接口(Service Endpoint Interface,SEI)是 WSDL A  is the Java representation of a WSDL port type.

存根

存根方法使用在从 WSDL 到 Java 映射阶段运行之前创建的特定于平台的存根。因为存根是在运行之前创建的,所以它有时称为 静态存根。它是一个实现 SEI 的 Java 类。从 WSDL 到 Java 的映射工具生成所需的客户端构件;该工具主要导入 WSDL 服务定义,然后创建相应的 Java 代码。构件包括 SEI、存根、(可选)Holder、序列化器、反序列化器和实用程序类。JAX-RPC 建议把存根的实例绑定到特定的协议和传输上,比如 SOAP 绑定存根。对于存根方法,需要执行的步骤如下:

获取一个 JAX-RPC 服务。

获得一个存根。

在该存根上调用 Web 服务的操作。

步骤2和3显示在 清单4中。请注意,使用 JAX-RPC Service 的 getPort 方法(在下一部分中进行描述)来获取存根也是有可能的。

清单 4. 通过存根访问 Web 服务

Hello myStub = (Hello) service.getHello();System.out.println(myStub.getGreeting("Jane");

此方法的优势在于它的简单性。基本上指需要两行代码来访问和调用 Web 服务的操作。然而,您需要知道开发时的 WSDL URL 并且运行您的从 WSDL 到 Java 的映射工具。另外,这些存根不是可移植的,因为它们依赖于实现类,并且不应该作为应用程序的一部分进行打包。可移植存根的设计超出了 JAX-RPC 1.0 和 1.1 的范围。

动态代理

您可以使用代理从 JAX-RPC Service中调用 Web 服务的操作。代理是实现 SEI 的 Java 类。获得代理使用的是 JAX-RPC Service 的getPort() 方法,它接受您想要调用的 Web 服务的端口的名称(存在于 WSDL 文档中)以及代理实现的 SEI。它之所以称为 动态是因为该代理是在运行时创建的。动态代理客户机的步骤如下:

获取一个 JAX-RPC Service 。

使用 JAX-RPC Service 的 getPort() 方法来获得一个代理以调用 Web 服务的操作。

在步骤1中,对于受管客户机,通过把 WSDL URL 以及 Web 服务名参数传送到 createService() 方法来获得 JAX-RPC Service 。对于 J2EE 容器管理的客户机,您通过 JNDI 查找来获取 JAX-RPC Service 。清单5展示了在 Web 服务上调用“getGreeting”操作的动态代理方法(步骤2)。

清单 5. 在动态代理上调用 Web 服务的操作

String namespace = "http://Hello.com";String portName = "Hello";QName portQN = new QName(namespace, portName);Hello myProxy = service.getPort(portQN, Hello.class);System.out.println(myProxy.getGreeting("Jane"));

这是所有您为了使用动态代理方法调用 Web 服务而需要编写的代码。使用这种方法的优势在于您可以编写可移植的、厂商无关的代码。然而,您需要知道开发时的 WSDL URL,并且需要在运行之前根据 WSDL 文档运行您的从 WSDL 到 Java 的映射工具。如果您没有这方面的信息,或者 WSDL URL 很可能改变,那么您应该改为使用 DII 方法。

动态调用接口(DII)

JAX-RPC Call 接口支持动态调用 Web 服务的操作。使用这种方法,您不需要知道开发时的 WSDL URL。JAX-RPC Service 用作实例化 JAX-RPC Call 的工厂,而不是从 JAX-RPC Service 中获得代理。此方法的步骤如下:

获取一个 JAX-RPC Service 。

使用 JAX-RPC Service 的 createCall() 方法实例化 JAX-RPC Call 。

使用它的 setter 方法来配置您的 Call 实例。

使用 JAX-RPC Call 的调用方法来调用 Web 服务的操作。

在步骤1中,对于受管客户机,仅仅通过把 Web 服务(而非 WSDL URL)的名称传送到 createService() 方法来从 JAX-RPC ServiceFactory 中获取 JAX-RPC Service 。对于 J2EE 容器管理的客户机,您通过 JNDI 查找来获取 JAX-RPC Service 。在步骤3中,配置参数为:操作的名称、端口号、目标服务端点的地址、返回类型。查阅 JAX-RPC 规范的8.2.4.2节可以获得标准的特性集的信息。步骤2到4示于 清单6中。

清单 6. 使用 DII 方法调用 Web 服务

String namespace = "http://Hello.com";String portName = "Hello";QName portQN = new QName(namespace, portName);String perationName = "getGreeting";Call call = service.createCall();call.setPortTypeName(portQN);call.setOperationName(new QName(namespace, operationName));call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY, "");call.setProperty(Call.OPERATION_STYLE_PROPERTY, "wrapped");call.addParameter("param1", ,ParameterMode.IN);call.setReturnType();Object[] inParams = new Object[] {"Jane"};String ret = (String) call.invoke(inParams);

您可以重用 Call 实例来调用 Web 服务上的其他操作。

注意:createCall() 和 addParameter() 方法有其他的签名。刚才描述的并不是调用它们的惟一方法。例如,使用端口类型名和操作名参数来调用 createCall() 也是可能的。

通过 Call 对象进行 DII 调用的过程比使用存根活动态代理复杂。然而,使用 DII Call 接口的优势在于,客户机可以调用远程过程而无需知道开发时的 WSDL URI 或 Web 服务操作的签名。这样当 Web 服务的细节改变时,很容易对代码进行修改。使用 DII 客户机,不需要像动态代理或静态存根的情形那样由从 WSDL 到 Java 的映射工具(Emitter)生成运行时类。然而,如果您知道您想要调用的 Web 服务不可能更改,就应该使用动态代理,因为配置 Call 实例可能很复杂。

动态发现和调用(DDI)

动态发现和调用(Dynamic Discovery and Invocation,DDI)是 Web 服务的灵活性使用的极至,其中,Web 可以动态发现和调用 Web 服务而无需预先知道它。虽然在 前面的部分中描述的 DII 客户机不需要知道 Web 服务的开发时细节,但是它们不涉及发现 Web 服务的过程。DDI 客户机执行三个步骤:

发现 UDDI 中 Web 服务的细节:查找提供服务的业务,然后查找描述该服务的 WSDL 文档的 URL。

读取 WSDL 文档来查找 Web 服务上的信息:名称空间、服务、端口和参数。

调用服务。

在步骤1中,UDDI Registry Enquiry API 用于浏览 UDDI 注册中心。在步骤2中,UDDI4J API 用于解析 WSDL 文档。最后,在步骤3中,使用 DII 方法(在 前面的部分中进行了描述)。要获得关于 DDI 的信息,您最好读一读 developerWorks 文章“Dynamic Discovery and Invocation of Web services”(列在 参考资料部分)。

结束语

本文描述了编写 Java Web 服务客户机代码的不同方式。如前所述,为了构建和运行所提供的客户机代码可能会需要一些构件(例如 JAX-RPC 或 JSR 109 类库、Java2WSDL 映射工具发出的存根、服务端点接口(Service Endpoint Interface,SEI)、部署描述符等等)。另外,还需要准备和运行“Hello” Web 服务实现。请查阅 Zip 归档文件中的 Readme 文件以获取关于如何构建和运行类的说明。

人总是珍惜未得到的,而遗忘了所拥有的

用Java客户机调用Web服务:J2SE和J2EE环境中Web服务客户机简介

相关文章:

你感兴趣的文章:

标签云: