用 JAX-RPC 构建 RPC 服务和客户机

使用 Java API 构建基于 RPC 的 Web 服务

远程过程调用(RPC)是基于 Simple Object Access Protocol(SOAP)或 Representational State Transfer(REST)的现代 Web 服务的前身。因为所有 Java™ 平台的 Web 服务 API 都构建在从 RPC 引入的概念之上,所以要想用 Java 语言编写有效且高效的 Web 服务,理解 Java API for XML-Based RPC(JAX-RPC)几乎是必需的。本教程讲解如何获取、安装和配置 JAX-RPC 并构建一个服务器端 RPC 接收器和一个简单的客户端应用程序。

Brett McLaughlin , 作家/编辑, O'Reilly Media

作者照片Brett McLaughlin 是非小说类作家中作品最畅销、屡获殊荣的作家。他撰写的计算机编程、家庭影院、分析与设计的图书销量超过 100,000 本。近十年来,他一直在撰写、编辑和出版技术图书,他在文字处理程序面前就象弹吉他一样轻松,他喜爱在家里与两个儿子捉迷藏,喜爱与妻子一起看着 Arrested Development 大笑。他的新书 获得了 2007 Jolt Technical Book 奖,而他的经典著作 仍然是在 Java 语言中使用 XML 技术的权威著作。



2008 年 8 月 06 日

开始之前

关于本教程

本教程完整地介绍如何安装、配置、构建和运行基于远程过程调用(RPC)的 Web 服务。我们将下载和安装一个 Java API for XML-Based RPC(JAX-RPC)实现,学习如何在 Java 类和包中使用 JAX-RPC,并构建客户机和服务器来支持基于 RPC 的交互。此外,还讨论配置选项,并帮助您熟悉如何部署基于 RPC 的应用程序。

目标

本教程全面介绍 JAX-RPC Web 服务的构建。更重要的是,学习所有 Web 服务的构建方式。本教程讨论在基于服务的体系结构中客户机-服务器交互的基本知识,并把 RPC 作为这些原理的一种实现来研究。

还将在实践背景下全面了解 JAX-RPC API。尽管我们并不使用每个类的每个方法,但是将讨论在真实环境中哪些类和方法是最基本的,以及哪些方法是不太 有用的。我们将在构建一个基于 RPC 的客户机和服务器的过程中讲解这些概念。

因为基于服务的体系结构与传统的客户机-服务器 Web 交互(比如通过 HTML 前端向 Java servlet 发出 POST 请求)相比不太直接,而且比较难以管理,所以 比较难实现。本教程讨论构建 Web 服务的一些最佳实践和常见错误。

还将:

  • 了解 JAX-RPC 的基础知识,因为它们与广泛的 Web 服务相关
  • 了解基于 RPC 的服务与基于 SOAP 和 REST 的服务之间的差异
  • 了解在什么情况下 RPC 服务是合适的选择

先决条件

本教程是为 Java 程序员编写的。您应该熟悉 Java 应用程序开发,熟悉如何使用标准的和第三方的 Java API 和工具集。

还需要一个能够驻留服务器端 Java 应用程序(servlet)的 Web 服务器。可以使用任何支持 Java 的 Web servlet 容器、应用服务器或驻留服务提供商。最流行的解决方案之一是 Apache Tomcat,这种产品是免费的,而且有良好的文档。由您自己决定是在(您公司或 ISP 的)远程服务器上测试程序,还是在本地机器上测试。只需在一台可访问的机器上安装和运行服务器即可。本教程会详细介绍如何在这些服务器上配置 JAX-RPC,所以目前您还不需要理解 JAX-RPC 的 servlet 和 Web 服务器之间的关系。

了解 Java servlet 和 Apache Tomcat(尤其是 servlet 驻留功能和 web.xml 部署描述符文件)会有帮助,但不是必需的。

关于这些主题的更多信息参见 参考资料 中的链接。

本教程主要关注 JAX-RPC 和 JAX-RPC 的 Apache Axis 开放源码实现,但是不要求读者具备 RPC、JAX-RPC 或 Apache Axis 知识。本教程会详细讨论它们的下载、安装和配置过程。


下载和安装 Apache Axis

因为 JAX-RPC 并不是标准 Java 发行版的组成部分,所以需要单独安装和配置。(正如稍后所解释的,实际上并不安装 JAX-RPC 本身,而是安装某种 JAX-RPC 实现)。目前不必考虑 RPC 是什么;我们将会进一步讨论 RPC。目前,只需按照以下步骤让 JAX-RPC 运行起来,这样就可以通过实践这个 API 和使用这个 API 的程序来学习相关知识。

检查 servlet 引擎

确认您的 servlet 引擎正在运行而且可以访问它,还要查明通过 Web 浏览器访问服务器所用的主机名和端口。如果在本地机器上安装了 Apache Tomcat,那么可以在 http://localhost:8080 上访问服务器的 Web 页面,您应该会看到与图 1 相似的页面:

图 1. 确认 Tomcat 正在运行
Tomcat 在初始安装时显示一个欢迎屏

显然,如果安装了其他 servlet 引擎,或者已经设置了 Tomcat 中的内容,就会看到不同的页面。无论如何,只要有可访问的 servlet 引擎,就可以继续了。

不需要安装 JAX-RPC

与 Java API for XML Binding(JAXB)或 Java API for XML Processing(JAXP),甚至 JDBC 等标准 API 一样,JAX-RPC 其实是一个 API 规范。换句话说,它仅仅是一个文档,其中规定了一组 Java 类和接口。这个文档描述 JAX-RPC 类和接口的行为;它并没有描述如何构建 JAX-RPC 应用程序,但是详细规定了涉及的组件以及如何用 Java 构造表示它们。

这个 API 还包含一组也称为 JAX-RPC 的类和接口(不同的东西都称为 “JAX-RPC”,这可能会引起混淆)。这些类和接口有时候称为语言绑定(尤其是在涉及 XML 规范时),但是它们仅仅 是由规范定义的构造。没有用来测试的示例类、示例代码或伪服务。

JAX-RPC 包含的类和接口都放在 javax.xml.rpc 包和几个子包中:

  • javax.xml.rpc.encoding
  • javax.xml.rpc.handler
  • javax.xml.rpc.handler.soap
  • javax.xml.rpc.holders
  • javax.xml.rpc.server
  • javax.xml.rpc.soap

javax.xml.rpc 包中的三个接口是核心组件:

  • javax.xml.rpc.Call
  • javax.xml.rpc.Service
  • javax.xml.rpc.Stub

在本教程中,您将了解关于这些接口和其他 JAX-RPC 包的更多信息。目前要注意,这三个核心组件是接口 而不是类。实际上,核心 JAX-RPC 包只包含很少几个具体类,其中的 NamespaceConstantsParameterMode 实际上是实用程序类。那么,类(也就是用 new 实例化的代码)在哪里呢?

JAX-RPC 把 API 与实现分隔开

JAX-RPC 的设计者定义了一个规范,然后编写了许多接口。这些接口定义类名和行为,但是它们没有实现 这些行为。生产商可以编写自己的 API 来实现 JAX-RPC 的标准接口。

您必须明白一点:JAX-RPC 本身没什么用。它有许多方法和接口,但是没有支持和实现它们的代码。因此,实际上 “安装 JAX-RPC” 是没有意义的。安装 JAX-RPC 实际上是指安装 JAX-RPC 的一种实现。为了方便,所有 JAX-RPC 接口都附带有可用的实现,而且经过适当的打包。所以尽管可以下载 JAX-RPC 规范文档(参见 参考资料),但是不需要安装 JAX-RPC,只需安装这个 API 的某种实现。

安装 Apache Axis

本教程使用的 JAX-RPC 实现是 Apache Axis。Axis 是免费的、开放源码且得到良好的支持。本教程使用 Apache Axis 1.4 而不是 Axis 2.0,因为后者不太适合 RPC 应用程序。Axis 1.4 仍然是当前支持的版本。

下载 Apache Axis 1.x

首先,访问 Apache Axis 1.x Web 站点的 Releases 页面。您会看到可以下载的版本列表,列表按版本号排序,见图 2:

图 2. 选择以 “1” 开头的 Apache Axis 最新版本
按版本排序的 Apache Axis 下载

选择最新版本;本教程使用 1.4 版。选择一个版本之后,可以选择一个镜像站点。最后,选择适合自己平台的二进制下载文件。Windows® 用户应该选择以 .zip 结尾的文件。Mac OS X 或 Linux® 用户应该选择 .tar.gz 版本。所以对于 Mac OS X 平台,选择 axis-bi-1_4.tar.gz;Windows 用户选择 axis-bin-1_4.zip。

展开 Apache Axis 并选择安装位置

展开您下载的包,会出现一个名称与 axis-1_4 相似的目录。把这个目录和其中的所有内容转移到一个长期位置,最好是您保存所有其他 Java 程序的位置。例如,在我的系统上,我把 Axis 目录移动到了 /usr/local/java:

[bdm0509:/usr/local/java] ls
apache-tomcat-6.0.16   axis-1_4      xalan-j_2_7_1

您可以选择自己喜欢的任何位置;选择 C:/Java 或主目录下的子目录是比较方便的。只需确保文件位于便于访问、不会被意外删除的位置。

现在需要创建一个 Web 应用程序,做一些基本配置,然后启动 Axis 服务。这是本教程要完成的下一个步骤;但是,首先需要解决关于 JAX-RPC 的一些基本问题。

JAX-RPC 和本教程过时了吗?

在安装 Axis 1.x 和学习本教程的过程中,您会看到一些 JAX-WS 参考资料反复指出 JAX-WS 将要替代 JAX-RPC。JAX-WS 确实将要替代 JAX-RPC;但是,这并不意味着 JAX-RPC 是完全无用或过时的。RPC 已经存在很长时间了,这是最干净的一种 Web 服务形式:长期运行的服务器端程序根据需要向客户机提供服务。服务提供某种对本身的描述,包括它需要的参数和它返回的数据。

尽管 JAX-WS 是基于 Java 的 Web 服务未来的发展方向,但是它使用与 JAX-RPC 相同的概念。因此,尽管语法不同,但是在迁移到 JAX-WS 时本教程讨论的原理仍然是非常有帮助的。另外,Axis 2.x 支持 JAX-WS;所以在迁移到 JAX-WS 时,本教程对 Axis 框架的介绍仍然是有用的。


检验 Axis 安装

在构建基于 RPC 的应用程序之前,先部署 Axis 附带的示例服务。这样可以非常简便地测试 Axis 和 JAX-RPC 安装,从而在进行开发之前确保系统正常。另外,通过这样的测试,还可以体验 RPC 的工作方式、服务的运行方式以及客户机如何访问这些服务。

安装 Apache Axis Web 应用程序

Apache Axis 附带一个示例 Web 应用程序,这个程序可以部署在任何 servlet 容器中。只需把这个 Axis Web 应用程序复制到 servlet 容器中驻留 Web 应用程序的地方,然后测试 Axis。

复制 Axis Web 应用程序

找到 servlet 引擎中部署 Web 应用程序的目录。这通常是一个称为 webapps/ 的目录。它常常直接嵌套在 servlet 引擎的根文件夹中。如果使用 Tomcat,这个目录直接嵌套在 Tomcat 根文件夹中;例如,在我的系统中,这是 apache-tomcat-6.0.16/webapps/ 文件夹。

现在,把 Axis 安装中的 webapps/ 目录中的 Axis 目录复制到 servlet 引擎的 webapps/ 目录。一定要复制 这个目录,而不是移动 它。这确保 Axis 安装中存在原来的 Web 应用程序。这样的话,如果修改 servlet 引擎的版本,原来的应用程序会成为备份,可以轻松地恢复。所以需要执行清单 1 所示的命令:

清单 1. 把 Axis Web 应用程序复制到 servlet 引擎的 webapps/ 目录
[bdm0509:/usr/local/java/apache-tomcat-6.0.16/webapps] 
cp -rp /usr/local/java/axis-1_4/webapps/axis .
[bdm0509:/usr/local/java/apache-tomcat-6.0.16/webapps] ls
ROOT      docs      host-manager
axis      examples   manager

启动 servlet 引擎

现在启动(或重新启动)servlet 引擎。可以使用命令行或 Web 界面。对于 Tomcat,只需使用命令关闭引擎并重新启动:

[bdm0509:/usr/local/java/apache-tomcat-6.0.16/bin] sh shutdown.sh
Using CATALINA_BASE:   /usr/local/java/apache-tomcat-6.0.16
Using CATALINA_HOME:   /usr/local/java/apache-tomcat-6.0.16
Using CATALINA_TMPDIR: /usr/local/java/apache-tomcat-6.0.16/temp
Using JRE_HOME:       /Library/Java/Home
[bdm0509:/usr/local/java/apache-tomcat-6.0.16/bin] sh startup.sh
Using CATALINA_BASE:   /usr/local/java/apache-tomcat-6.0.16
Using CATALINA_HOME:   /usr/local/java/apache-tomcat-6.0.16
Using CATALINA_TMPDIR: /usr/local/java/apache-tomcat-6.0.16/temp
Using JRE_HOME:       /Library/Java/Home

只要没有看到任何错误,就说明这个 Axis Web 应用程序已经安装好了。

测试和检验安装

一些 servlet 引擎会热部署 Web 应用程序

可能不需要重新启动 servlet 引擎。许多 servlet 引擎会自动部署放到引擎的 webapps/ 目录中的任何 WAR(Web 存档)文件或应用程序。但是,停止并重新启动引擎仍然是一种好做法,这会确保应用程序生效。另外,这使 servlet 引擎能够在启动 Axis 安装时报告错误或警告,这个预防措施有助于发现潜在的问题。

找到刚才安装的 Axis Web 应用程序。通常,只需输入 servlet 引擎的 URL、前向斜杠(/)和 Web 应用程序的名称:axis。因此,如果 servlet 引擎驻留在 http://localhost:8080,Axis 完整的 URL 就是 http://localhost:8080/axis/。应该会看到与图 3 相似的屏幕:

图 3. Axis 1.x 的默认 Web 页面
Axis Web 应用程序的主页链接到几个 Axis 示例应用程序

这说明这个 Web 应用程序正在运行,但是并不 保证 Axis 所需的所有东西都已经安装了。所以需要进行检验。为了检验 Axis 安装,单击主页上的第一个链接 “Validation”。应该会看到与图 4 相似的屏幕:

图 4. Apache Axis 的检验页面(包含几个错误)
Axis 检验 JSP 指出缺少 Axis 所需的库

这是一个 JavaServer Page(JSP),它会检验安装并报告缺少的组件。图 4 所示的示例指出了三个问题:

  • 缺少必需的 javax.activation.DataHandler 类。
  • 缺少 javax.mail.internet.MimeMessage helper 类。
  • 缺少 org.apache.xml.security.Init helper 类。

这个页面的优点是,它明确说明了应该如何处理这些错误。对于每个错误,都会报告缺少的类以及包含这个类的 Java Archive(JAR)或库,还提供下载缺少的组件的链接。

下载缺少的组件

本教程的 参考资料 没有提供 Axis 所需的库的链接。Axis 正在不断变化(即使在 1.x 系列中也是如此),所以通过检验页面获取 Axis 当前所需的库和链接更合适。可以把这个页面作为在线参考资料页面,帮助您快速简洁地设置 Axis。

得到缺少的组件的完整列表之后,应该下载所有这些组件。首先单击各个组件的链接。下载引用的每个库,根据需要展开库,找到 Axis 检验页面上列出的 JAR 文件。

例如,对于 Java Activation Framework,单击 Axis 页面上的链接并单击 java.sun.com 下载链接。最终会下载一个 ZIP 文件,它可以展开成一个目录:jaf-1.0.2。在这个目录中有所需的 JAR 文件 activation.jar。把这个文件复制到 servlet 引擎的 lib/ 目录:

[bdm0509:/usr/local/java/apache-tomcat-6.0.16/lib] 
    cp ~/Downloads/jaf-1.0.2/activation.jar .

对于缺少的其他组件,重复这个步骤。可能需要搜索引用的每个页面,寻找正确的下载链接,但是对于每个组件,只需一两次单击就能够完成下载(还常常需要接受软件许可协议)。

下载所有的库之后,重新启动 servlet 引擎。servlet 引擎无法动态地装载库,所以必须重新启动。然后,再次访问 Axis 主页,单击 Validation 链接,检查是否还有问题。

获取(或省略)XML Security

Axis 有一个可选组件 XML Security(在 图 4 中 Optional Components 下面列出),对于是否使用这个组件,有很多争议。XML Security 实际上需要一个第三方库,在下载 XML Security 时并不会 在下载包中得到这个库。更糟糕的是,手工下载这个文件并不能解决问题。实际上,需要从源代码构建 XML Security,这需要设置和运行 Ant、JUnit 和其他几个第三方工具。因此,为了使用这个可能不常用的库,需要完成许多与 RPC 不相关的工作。

如果您是经验丰富的开发人员,那么可以花时间下载 XML Security 源代码。阅读 XML Security 包含的 INSTALL 文件,下载所有第三方库,然后从源代码构建 JAR 文件。然后,把这些 JAR 文件复制到 Tomcat 或 servlet 引擎中。

如果您不是 Java 高手,或者只想试试 JAX-RPC 和 Axis,那么可以跳过这个步骤。在检验页面上会出现与图 5 相似的结果:

图 5. 没有使用 XML Security 时的 Axis 检验页面
这个页面报告所有必需的组件已经安装了,但是没有安装 XML Security

本教程并不需要 XML Security,所以如果得到与图 5 相似的结果,Axis 就安装好了。

测试一个简单的 Java Web 服务

在构建自己的 Web 服务之前,应该再执行一个确认步骤。到目前为止,已经检验了安装,但是还没有测试实际的 Web 服务调用。再次访问 Axis 主页(见 图 3),单击 “Call” 链接。这会运行一个 Web 服务,从而确认客户端和服务器端组件都正常工作。应该会看到与图 6 相似的结果:

图 6. Axis 附带一个示例 Java Web 服务
示例 Web 服务在浏览器上显示请求头

这看起来不太明白。这是因为浏览器(在图 6 中是 Safari 浏览器)尝试把这个调用的输出显示为更友好的形式。可以单击 View > Source 来查看原始输出。(根据您使用的浏览器,可能不需要这个步骤)。清单 2 给出 XML 源代码:

清单 2. 示例 Web 服务返回 XML 版本的请求头
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <listResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
   <listReturn soapenc:arrayType="xsd:string[8]" xsi:type="soapenc:Array" 
               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
    <listReturn xsi:type="xsd:string">user-agent:Mozilla/5.0 
                (Macintosh; U; PPC Mac OS X 10_5_2; en-us) AppleWebKit/525.18 
             (KHTML, like Gecko) Version/3.1.1 Safari/525.18</listReturn>
    <listReturn xsi:type="xsd:string">referer:http://localhost:8080/axis/
      </listReturn>
    <listReturn xsi:type="xsd:string">accept:text/xml,application/xml,
         application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,
         */*;q=0.5</listReturn>
    <listReturn xsi:type="xsd:string">accept-language:en-us</listReturn>
    <listReturn xsi:type="xsd:string">accept-encoding:gzip, deflate</listReturn>
    <listReturn xsi:type="xsd:string">cookie:JSESSIONID=
      42A37ED6763ECC773D5FEB70484D57B1</listReturn>
    <listReturn xsi:type="xsd:string">connection:keep-alive</listReturn>
    <listReturn xsi:type="xsd:string">host:localhost:8080</listReturn>
   </listReturn>
  </listResponse>
 </soapenv:Body>
</soapenv:Envelope>

这些头仍然有点儿混乱(其中有许多与 SOAP 相关的 XML,而且为了提高可读性,清单 2 的格式实际上已经调整过了)。但这里的要点是,响应是 XML,而不是一个错误。如果获得与清单 2 相似的结果,就说明系统正常。现在已经安装了 Axis,简单的 Java Web 服务调用也已经正常工作了。


构建一个程序,并将其发布为服务

在 JAX-RPC 和其他任何 Web 服务框架中,最出色的特性之一是,在编写作为 Web 服务发布的程序时不需要考虑 RPC 或 Web 服务。大多数 Web 服务最初并不是作为 Web 服务开发的;实际上,它们最初是一般的程序,包含一些在调用时返回值的方法。如果您熟悉这个概念,就说明已经理解了 Web 服务的本质:它们仅仅是可以通过 Web 而不是虚拟机访问的程序。

所以,在开始关注 RPC 语法或 Web Services Description Language(WSDL)之前,我们需要一个可供 Web 客户机使用的类。

构建 Java 类

假设您希望开发一个简单的图书搜索工具。这个程序存储与认知科学、学习理论和用户界面设计相关的图书。但是,因为这些书的内容非常深奥,书名常常无法反映书的内容,所以这个程序必须能够按照指定的关键字搜索存储库,并返回与这个关键字相关的书。例如,对关键字 presentation 的搜索可能返回 Garr Reynolds 所著的 Presentation Zen 和 Dan Roam 所著的 The Back of the Napkin。第一个书名本身就符合条件,但是这个程序很聪明,可以找到第二本书,而一般的搜索程序很可能找不到它。

目前,还不需要为 Web 服务或 JAX-RPC 操心。我们只需要一个搜索程序,以后将把它转换为 Web 服务。

定义类和方法调用

首先编写一个简单的类骨架,定义希望提供给程序用户的方法。清单 3 给出一个 Java 类,它接受一个搜索词并返回一个书名列表:

清单 3. 返回书名的完整程序的骨架
package dw.ibm;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class BookSearcher {

  private Map books;

  public BookSearcher() {
    books = new HashMap();
  }

  public void setBooks(Map books) {
    this.books = books;
  }

  public void addBook(String title, List keywords) {
    books.put(title, keywords);
  }

  public List getKeywords(String title) {
    return (List)books.get(title);
  }

  public void addKeyword(String title, String keyword) {
    List keywords = (List)books.get(title);
    if (keywords != null) {
      keywords.add(keyword);
      // No need to manually "re-put" the list
    } else {
      keywords = new LinkedList();
      keywords.add(keyword);
      books.put(title, keywords);
    }
  }

  public List search(String keyword) {
    List results = new LinkedList();
    // logic to be implemented

    return results;
  }
}

这相当简单。每本书作为一个条目存储在一个映射中。映射的键是书名,因此很容易在书名中搜索关键字。另外,每本书有一个相关联的关键字列表。清单 3 中没有表现出这一点,但是这些关键字仅仅是 “presentation”、“cognitive science” 或 “marketing” 等字符串值。

这个程序包含实现以下功能的方法:

  • 通过书名和关键字列表添加书
  • 为给定书的添加关键字
  • 获取书的关键字

这些方法既是实用程序(供管理员使用),也具有功能性(帮助使用这个程序的用户获得关于书的信息)。这个程序还有一个搜索方法,这是关键功能:给出一个关键字,就会返回所有匹配的书名。剩下的工作仅仅是实现搜索逻辑并装载一些图书信息。

参数化列表和泛化类型在哪里?

输入或下载(参见 下载)清单 3 中的代码并编译。如果您仍然使用 Java 1.4,这段代码可以正常编译。如果在 Java 5 或更高版本上编译,就会收到几个警告,因为 List 未经检查而且无类型。很容易添加类型,而且值得这么做。为关键字列表设置类型非常有益:确保只能把字符串关键字添加到列表中,从而使程序更安全。但是,目前的程序仍然非常明确,很容易理解,这对于本教程很重要。

编写搜索功能

通过关键字搜索书名非常简单,只需循环遍历图书的映射,检查每本书的列表是否包含指定的关键字。同样,这里不涉及任何 Web 服务概念;它仅仅是基本的程序逻辑。清单 4 给出 BookSearcher 中完整的 search() 方法:

清单 4. 按照关键字搜索图书的 search() 方法
public List search(String keyword) {
  List results = new LinkedList();

  for (Iterator i = books.keySet().iterator(); i.hasNext(); ) {
    String title = (String)i.next();
    List keywords = (List)books.get(title);
    if (keywords.contains(keyword)) {
      results.add(title);
    }
  }

  return results;
}

这个方法循环遍历存储库中的所有书,取出每本书的关键字列表。检查列表中是否包含与指定的关键字匹配的条目。然后,通过另一个列表返回匹配的书。

添加一些示例数据

最后,需要一些示例数据。一般情况下,这些数据可能存储在数据库中。但是,这个程序只是为了演示 JAX-RPC 技术,所以只需用一个简单的 addBooks() 方法(见清单 5)添加一些书名和关键字:

清单 5. 提供图书数据的 addBooks() 方法
private void addBooks() {  List keywords = new LinkedList();
  keywords.add("presentation"); keywords.add("Keynote");
  keywords.add("PowerPoint"); keywords.add("design");
  addBook("Presentation Zen", keywords);

  List keywords2 = new LinkedList();
  keywords2.add("presentation"); keywords2.add("user interface design");
  keywords2.add("pictures"); keywords2.add("visuals");
  addBook("The Back of the Napkin", keywords2);

  List keywords3 = new LinkedList();
  keywords3.add("marketing"); keywords3.add("business");
  keywords3.add("commercials"); keywords3.add("consumers");
  addBook("Purple Cow", keywords3);

    List keywords4 = new LinkedList();
  keywords4.add("marketing"); keywords4.add("business");
  keywords4.add("notecards"); keywords4.add("design");
  keywords4.add("visuals"); keywords4.add("pictures");
  keywords4.add("humor");
  addBook("Indexed", keywords4);

  List keywords5 = new LinkedList();
  keywords5.add("marketing"); keywords5.add("business");
  keywords5.add("design"); keywords5.add("emotion");
  keywords5.add("functionality"); keywords5.add("consumers");
  addBook("Emotional Design", keywords5);
  keywords.clear();
}

这里没有什么值得关注的地方;现在只需调用 addBooks() 方法。清单 6 给出 BookSearcher 的完整版本,其中在构造函数中调用 addBooks(),从而自动地填充一些书名和关键字:

清单 6. 完整的 BookSearcher
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class BookSearcher {

  private Map books;

  public BookSearcher() {
    books = new HashMap();

    // for example purposes
    addBooks();
  }

  public void setBooks(Map books) {
    this.books = books;
  }

  public void addBook(String title, List keywords) {
    books.put(title, keywords);
  }

  public void addKeyword(String title, String keyword) {
    List keywords = (List)books.get(title);
    if (keywords != null) {
      keywords.add(keyword);
      // No need to manually "re-put" the list
    } else {
      keywords = new LinkedList();
      keywords.add(keyword);
      books.put(title, keywords);
    }
  }

  public List getKeywords(String title) {
    return (List)books.get(title);
  }
  public List search(String keyword) {
    List results = new LinkedList();

    for (Iterator i = books.keySet().iterator(); i.hasNext(); ) {
      String title = (String)i.next();
      List keywords = (List)books.get(title);
      if (keywords.contains(keyword)) {
        results.add(title);
      }
    }

    return results;
  }

  private void addBooks() {
    List keywords = new LinkedList();
    keywords.add("presentation"); keywords.add("Keynote");
    keywords.add("PowerPoint"); keywords.add("design");
    addBook("Presentation Zen", keywords);

    List keywords2 = new LinkedList();
    keywords2.add("presentation"); keywords2.add("user interface design");
    keywords2.add("pictures"); keywords2.add("visuals");
    addBook("The Back of the Napkin", keywords2);

    List keywords3 = new LinkedList();
    keywords3.add("marketing"); keywords3.add("business");
    keywords3.add("commercials"); keywords3.add("consumers");
    addBook("Purple Cow", keywords3);

    List keywords4 = new LinkedList();
    keywords4.add("marketing"); keywords4.add("business");
    keywords4.add("notecards"); keywords4.add("design");
    keywords4.add("visuals"); keywords4.add("pictures");
    keywords4.add("humor");
    addBook("Indexed", keywords4);

    List keywords5 = new LinkedList();
    keywords5.add("marketing"); keywords5.add("business");
    keywords5.add("design"); keywords5.add("emotion");
    keywords5.add("functionality"); keywords5.add("consumers");
    addBook("Emotional Design", keywords5);
    keywords.clear();
  }
}

现在,有了一个可以运行的程序,但是其中没有任何 JAX-RPC 代码。这就是使用 JAX-RPC 这样的 API 比编写 Java servlet 或 JSP 更方便的原因。在编写 servlet 或 JSP 时,代码从一开始就与服务器端的情况相关;也可以编写一个类,但是 servlet 必须了解通过调用传递和返回的数据的细节。在使用 JAX-RPC 时,编写的是一般的 Java 类,不包含与服务器端或 Web 服务相关的调用,然后再添加 JAX-RPC。

(在转换为服务之前)测试代码

在把 JAX-RPC 集成到程序中时,会显著增加复杂性:调用可以来自客户机、JAX-RPC API、servlet 引擎等地方。应该在执行这个步骤之前测试代码,确保业务逻辑和应用程序逻辑都符合预期。这样的话,如果以后遇到了麻烦,就可以确定问题(在大多数情况下)出现在 RPC 组件中,而与类的逻辑无关。

清单 7 是一个简单的测试用例,可以从命令行运行它;它仅仅调用几个方法并输出结果,让我们可以检验结果是否正确:

清单 7. BookSearcher 的测试类
import java.util.Iterator;
import java.util.List;

public class BookSearchTester {

  public static void main(String[] args) {
    BookSearcher searcher = new BookSearcher();
    List keywords = searcher.getKeywords("Purple Cow");
    System.out.println("Keywords for 'Purple Cow':");
    for (Iterator i = keywords.iterator(); i.hasNext(); ) {
      System.out.println("  " + (String)i.next());
    }

    List books = searcher.search("design");
    System.out.println("Books that match the keyword 'design':");
    for (Iterator i = books.iterator(); i.hasNext(); ) {
      System.out.println("  " + (String)i.next());
    }
  }
}

编译并运行清单 7 中的代码。应该会看到与清单 8 相似的结果集:

清单 8. 测试 BookSearcher 类的基本功能
[bdm0509:~/Documents/developerworks/jax-rpc] java BookSearchTester
Keywords for 'Purple Cow':
  marketing
  business
  commercials
  consumers
Books that match the keyword 'design':
  Emotional Design
  Indexed

把类转换为 RPC 服务

有了 Java 类并在 servlet 引擎中设置和配置了 Axis 之后,就需要构建一个可供消费的 RPC 服务。

但是 RPC 什么?

RPC 是远程(比如从另一台机器)过程(比如一个方法)调用。换句话说,RPC 意味着调用另一台机器上的一个方法。它实际上就这么简单;编写一个类,让它的一个或多个方法可供程序调用,这些程序不必在相同的虚拟机或物理机器中

对于 BookSearcher,这意味着可以把 BookSearcher 类放在某处的一台 Web 服务器上,然后在本地机器上运行使用这个类的程序。尽管肯定有一些与 RPC 相关的活动,尤其是在客户机上,但是您的程序可以像任何其他两个类一样进行交互。可以调用一个方法,向它发送参数,然后获取响应。

为什么不是远程方法 调用?

RPC 技术早在 C# 和 Java 等面向对象语言成为主流之前就出现了。实际上,RPC 原来是为 C 应用程序开发的,主要也用在这种应用程序中;在这种应用程序中,函数是公开行为的主要方法。当 RPC 服务出现在 Java、C++ 和 C# 程序中时,因为许多开发人员熟悉 RPC 的概念,使用 RPC 这个词更有意义,所以没有采用远程方法调用(RMC)这样的新词汇。

因此,在 RPC 环境中,可以认为函数方法 是相似,不需要关注这两个词的语义差异。

服务器端类是 RPC 中的服务

现在,有了一个类(BookSearcher),它将放在服务器端。目前它只是一个一般的 Java 类,但是如果可以通过 RPC 访问其中的一些方法,这个类就成了一个服务。它向客户机提供可以调用的函数(方法)。进行调用的代码被称为客户机调用者

让方法可供远程调用使用就是公开了 这个方法。所以,必须选择要通过 RPC 公开 哪些方法。可以公开一个方法、所有方法或一部分方法。客户机可以调用公开的方法,而且只能调用公开的方法。把类转换为服务,然后公开服务的一些方法,这个过程称为发布 服务。所以我们要公开一个发布的服务的一些方法。

但是我只有一台机器!

即使无法访问 Web 服务器,也可以继续学习本教程。在通过 Axis 以服务形式公开一个类之后,需要通过网络调用访问这个类。但是,也可以在本地机器上运行另一个类来访问这个服务,这是一种不错的测试用例,而且您不会错过任何步骤。网络调用会被路由回您自己的机器,不需要经过因特网;即使如此,这仍然是网络调用,这就够了。即使您没有另一台机器,在本地机器上运行所有代码也是一样的。

当然,如果能够 把服务类放在另一台运行 servlet 引擎的机器,就能够在完全成熟的远程环境中看到 RPC 的效果。还可以体验到发出请求和接收响应所需的时间(这些时间与建立网络连接所需的时间相比可以忽略不计)。还会体验真正的 RPC 环境。

用 Java Web Service(JWS)发布服务

在客户机上,需要做一些工作来连接到启用 RPC 的服务。但是,实际部署服务是很繁琐的。因为需要维护每个 RPC 包或工具集(比如 Axis)的与工具集相关的细节。不需要学习在 JAX-RPC 上构建的 API,只需利用工具集来公开服务。

把 .java 文件复制到 .jws 文件中

在使用 Axis 时,发布服务最简便最快速的方法是使用 JWS 文件。只需把 Java 源代码文件(扩展名为 .java)复制到一个扩展名为 .jws 的同名文件中。然后,把这个 .jws 文件放在 Axis Web 应用程序中,这个应用程序应该在 servlet 引擎的 webapps/axis 目录中。所以要想发布 BookSearcher,应该执行清单 9 所示的命令:

清单 9. 把 Java 类转换为 JWS 文件
	[bdm0509:/usr/local/java/apache-tomcat-6.0.16] 
    cp ~/Documents/developerworks/jax-rpc/BookSearcher.java webapps/axis/BookSearcher.jws

这种做法看起来相当古怪;修改文件的扩展名通常不是好做法。但是,这正是 Axis 所需要的。当 Axis Web 应用程序在它的目录中 “看到” .jws 文件时,它会把这个文件编译为 Java Web 服务,并构建所需的所有 SOAP 访问代码,让客户机可以访问这个类。Axis 甚至会马上部署这个服务。

通过 Web 浏览器访问服务

如果 servlet 引擎还没有启动的话,就启动它。可以通过 http://hostname:port/axis 访问 Axis Web 应用程序。如果在本地机器上使用 Tomcat 的默认安装,这个 URL 就是 http://localhost:8080/axis。然后,加上一个前向斜杠、类名和 .jws 扩展名。所以要想访问上面的示例服务,应该输入 URL http://localhost:8080/axis/BookSearcher.jws。Web 浏览器应该会报告在这个地址上确实有一个 Web 服务正在运行,见图 7:

图 7. Axis 部署服务并使它可以接受 Web 访问
Axis 提供一个简单的消息,指出 Web 服务可用

还会看到 “Click to see the WSDL” 链接。WSDL 是一个新词汇,但这是一个非常重要的概念。

WSDL 描述服务

网络服务描述语言(Web Services Description Language,WSDL)是一种 XML 词汇表,用来为访问服务的客户机代码描述服务。WSDL 描述:

  • 可用的方法
  • 每个方法的参数
  • 每个方法的返回值

从本质上说,WSDL 文件是与 Web 服务进行交互的规范。

单击 BookSearcher Web 服务上的 “Click to see the WSDL” 链接,看看这个服务的 WSDL。如果浏览器尝试解释 XML,可以选择 View > Source 来查看原始格式的 WSDL。BookSearcher 服务的 WSDL 见清单 10:

清单 10. BookSearcher 服务的 WSDL 描述可用的方法
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://localhost:8080/axis/BookSearcher.jws" 
                  xmlns:apachesoap="http://xml.apache.org/xml-soap" 
                  xmlns:impl="http://localhost:8080/axis/BookSearcher.jws" 
                  xmlns:intf="http://localhost:8080/axis/BookSearcher.jws" 
                  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
                  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
                  xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" 
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!--WSDL created by Apache Axis version: 1.4
Built on Apr 22, 2006 (06:55:48 PDT)-->
 <wsdl:types>
  <schema targetNamespace="http://xml.apache.org/xml-soap" 
          xmlns="http://www.w3.org/2001/XMLSchema">
   <import namespace="http://localhost:8080/axis/BookSearcher.jws"/>
   <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
   <complexType name="mapItem">
    <sequence>
     <element name="key" nillable="true" type="xsd:anyType"/>
     <element name="value" nillable="true" type="xsd:anyType"/>
    </sequence>
   </complexType>
   <complexType name="Map">
    <sequence>
     <element maxOccurs="unbounded" minOccurs="0" 
              name="item" type="apachesoap:mapItem"/>
    </sequence>
   </complexType>
   <complexType name="Vector">
    <sequence>
     <element maxOccurs="unbounded" minOccurs="0" 
              name="item" type="xsd:anyType"/>
    </sequence>
   </complexType>
  </schema>
  <schema targetNamespace="http://localhost:8080/axis/BookSearcher.jws" 
          xmlns="http://www.w3.org/2001/XMLSchema">
   <import namespace="http://xml.apache.org/xml-soap"/>
   <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
   <complexType name="ArrayOf_xsd_anyType">
    <complexContent>
     <restriction base="soapenc:Array">
      <attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:anyType[]"/>
     </restriction>
    </complexContent>
   </complexType>
  </schema>
 </wsdl:types>

   <wsdl:message name="addBookResponse">
   </wsdl:message>

   <wsdl:message name="addBookRequest">
      <wsdl:part name="title" type="xsd:string"/>
      <wsdl:part name="keywords" type="impl:ArrayOf_xsd_anyType"/>
   </wsdl:message>

   <wsdl:message name="searchRequest">
      <wsdl:part name="keyword" type="xsd:string"/>
   </wsdl:message>

   <wsdl:message name="getKeywordsRequest">
      <wsdl:part name="title" type="xsd:string"/>
   </wsdl:message>

   <wsdl:message name="addKeywordRequest">
      <wsdl:part name="title" type="xsd:string"/>
      <wsdl:part name="keyword" type="xsd:string"/>
   </wsdl:message>

   <wsdl:message name="setBooksResponse">
   </wsdl:message>

   <wsdl:message name="searchResponse">
      <wsdl:part name="searchReturn" type="impl:ArrayOf_xsd_anyType"/>
   </wsdl:message>

   <wsdl:message name="setBooksRequest">
      <wsdl:part name="books" type="apachesoap:Map"/>
   </wsdl:message>

   <wsdl:message name="addKeywordResponse">
   </wsdl:message>

   <wsdl:message name="getKeywordsResponse">
      <wsdl:part name="getKeywordsReturn" type="impl:ArrayOf_xsd_anyType"/>
   </wsdl:message>

   <wsdl:portType name="BookSearcher">
      <wsdl:operation name="setBooks" parameterOrder="books">
         <wsdl:input message="impl:setBooksRequest" name="setBooksRequest"/>
         <wsdl:output message="impl:setBooksResponse" name="setBooksResponse"/>
      </wsdl:operation>
      <wsdl:operation name="addBook" parameterOrder="title keywords">
         <wsdl:input message="impl:addBookRequest" name="addBookRequest"/>
         <wsdl:output message="impl:addBookResponse" name="addBookResponse"/>
      </wsdl:operation>
      <wsdl:operation name="addKeyword" parameterOrder="title keyword">
         <wsdl:input message="impl:addKeywordRequest" name="addKeywordRequest"/>
         <wsdl:output message="impl:addKeywordResponse" name="addKeywordResponse"/>
      </wsdl:operation>
      <wsdl:operation name="getKeywords" parameterOrder="title">
         <wsdl:input message="impl:getKeywordsRequest" name="getKeywordsRequest"/>
         <wsdl:output message="impl:getKeywordsResponse" name="getKeywordsResponse"/>
      </wsdl:operation>
      <wsdl:operation name="search" parameterOrder="keyword">
         <wsdl:input message="impl:searchRequest" name="searchRequest"/>
         <wsdl:output message="impl:searchResponse" name="searchResponse"/>
      </wsdl:operation>
   </wsdl:portType>

   <wsdl:binding name="BookSearcherSoapBinding" type="impl:BookSearcher">
      <wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
      <wsdl:operation name="setBooks">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="setBooksRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://DefaultNamespace" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="setBooksResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://localhost:8080/axis/BookSearcher.jws" 
                           use="encoded"/>
         </wsdl:output>
      </wsdl:operation>

      <wsdl:operation name="addBook">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="addBookRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://DefaultNamespace" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="addBookResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://localhost:8080/axis/BookSearcher.jws" 
                           use="encoded"/>
         </wsdl:output>
      </wsdl:operation>

      <wsdl:operation name="addKeyword">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="addKeywordRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://DefaultNamespace" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="addKeywordResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://localhost:8080/axis/BookSearcher.jws" 
                           use="encoded"/>
         </wsdl:output>
      </wsdl:operation>

      <wsdl:operation name="getKeywords">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="getKeywordsRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://DefaultNamespace" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="getKeywordsResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://localhost:8080/axis/BookSearcher.jws" 
                           use="encoded"/>
         </wsdl:output>
      </wsdl:operation>

      <wsdl:operation name="search">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="searchRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://DefaultNamespace" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="searchResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://localhost:8080/axis/BookSearcher.jws" 
                           use="encoded"/>
         </wsdl:output>
      </wsdl:operation>
   </wsdl:binding>

   <wsdl:service name="BookSearcherService">
      <wsdl:port binding="impl:BookSearcherSoapBinding" name="BookSearcher">
         <wsdlsoap:address location="http://localhost:8080/axis/BookSearcher.jws"/>
      </wsdl:port>
   </wsdl:service>
</wsdl:definitions>

这个 WSDL 很详细,也相当长。但是,它是理解 Web 服务和连接 Web 服务的代码的关键。本教程的下一节详细分析这个 WSDL,为编写远程搜索图书的代码做准备。


分析 WSDL 来理解服务

如果仔细看看上一节中的 WSDL,就可以看出 WSDL 究竟描述了什么。但是请牢记,在许多情况下,您无法获得要使用的服务的源代码。您得到的只有 WSDL。在这种情况下,理解 WSDL 对于正确有效地使用 RPC 服务(或任何其他类型的 Web 服务)非常重要。

WSDL 包含大量名称空间信息

程序员和文档作者常常不重视(甚至完全忽视)WSDL 文件中的根元素声明。但是,在 WSDL 中,这个声明包含大量信息,见清单 11:

清单 11. 根元素声明
<wsdl:definitions targetNamespace="http://localhost:8080/axis/BookSearcher.jws" 
   xmlns:apachesoap="http://xml.apache.org/xml-soap" 
  xmlns:impl="http://localhost:8080/axis/BookSearcher.jws" 
  xmlns:intf="http://localhost:8080/axis/BookSearcher.jws" 
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">

每个 xmlns: 属性定义一个名称空间和相关联的前缀。所以这里有 Apache SOAP 名称空间、SOAP 编码名称空间、WSDL 和 WSDL SOAP 名称空间、XML Schema 名称空间等等。还设置了目标名称空间,它的统一资源定位符(URI)是代表发布的服务的 JWS 文件。

好消息是,尽管这些名称空间对于 SOAP、RPC、Axis、XML 和在 Web 服务中使用的几乎所有其他技术都很重要,但是不需要太为它们操心。WSDL 中的大多数元素由一个 WSDL 规范定义并与 wsdl 前缀相关联,XML Schema 名称空间(以及相关联的类型)与 xsd 前缀相关联,知道这些就够了。其他名称空间也有用,但是在编写有效的 Web 服务客户机时不需要理会它们。

WSDL 定义基于对象的类型

WSDL 的下一个关键部分包含在 <wsdl:types> 元素中,见清单 12:

清单 12. <wsdl:types> 元素
<wsdl:types>
  <schema targetNamespace="http://xml.apache.org/xml-soap" 
          xmlns="http://www.w3.org/2001/XMLSchema">
   <import namespace="http://localhost:8080/axis/BookSearcher.jws"/>
   <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
   <complexType name="mapItem">
    <sequence>
     <element name="key" nillable="true" type="xsd:anyType"/>
     <element name="value" nillable="true" type="xsd:anyType"/>
    </sequence>
   </complexType>
   <complexType name="Map">
    <sequence>
     <element maxOccurs="unbounded" minOccurs="0" 
              name="item" type="apachesoap:mapItem"/>
    </sequence>
   </complexType>
   <complexType name="Vector">
    <sequence>
     <element maxOccurs="unbounded" minOccurs="0" 
              name="item" type="xsd:anyType"/>
    </sequence>
   </complexType>
  </schema>
  <schema targetNamespace="http://localhost:8080/axis/BookSearcher.jws" 
          xmlns="http://www.w3.org/2001/XMLSchema">
   <import namespace="http://xml.apache.org/xml-soap"/>
   <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
   <complexType name="ArrayOf_xsd_anyType">
    <complexContent>
     <restriction base="soapenc:Array">
      <attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:anyType[]"/>
     </restriction>
    </complexContent>
   </complexType>
  </schema>
</wsdl:types>

WSDL 和 Web 服务了解一些基本类型,比如 stringintfloat,但是不了解比 Java 基本数据类型更复杂的类型。但是,BookSearcher 类的一些方法能够接受或返回列表和映射。为了处理这些基于对象的复杂类型,WSDL 必须通过 XML Schema 类型定义它们。文档的这个部分定义所有这些类型。例如,清单 13 给出一个映射的定义,使 RPC 客户机和服务可以理解这个映射:

清单 13. 定义 RPC 客户机和服务可以理解的一个映射
<complexType name="mapItem">
    <sequence>
     <element name="key" nillable="true" type="xsd:anyType"/>
     <element name="value" nillable="true" type="xsd:anyType"/>
    </sequence>
   </complexType>
   <complexType name="Map">
    <sequence>
     <element maxOccurs="unbounded" minOccurs="0" 
              name="item" type="apachesoap:mapItem"/>
    </sequence>
</complexType>

Vector 类型以同样的方式代表列表,提供了列表的上限和下限。这对使用 WSDL 造成了一定的困难,因为只有在编写了一些服务和客户机之后,才会逐渐熟悉 Java 对象和定制 WSDL 类型之间的基本映射。尽管如此,如果看到一个方法以 Vector 作为参数,那么只需在 <wsdl:types> 元素中寻找关于这个类型的信息,包括对其中的值的约束。

每次发送和返回的都是消息

下一个元素由 <wsdl:message> 表示。这里有一些源自 Java 的概念,它们主要关注与网络和 SOAP 相关的问题。在向服务中的方法发送请求时,实际上是发送一个消息。如果请求的方法没有参数,消息就不包含方法所操作的任何数据。如果方法需要参数数据,就必须在消息中发送数据。

服务从方法返回时也是如此:返回的消息可以包含来自方法的数据,也可以没有数据。但是,关键在于发送和接收的是单独的 消息。一个消息从客户机发送到服务,另一个消息从服务返回到客户机。这两个消息在逻辑上相关,但是在编程或技术范畴上没有联系。

因此,必须声明和定义这些消息。以 BookSearchergetKeywords() 方法为例。它接收一个字符串标题参数,返回一个列表。必须在 WSDL 中声明这两个消息:

清单 14. WSDL 中的消息声明
<wsdl:message name="getKeywordsRequest">
      <wsdl:part name="title" type="xsd:string"/>
   </wsdl:message>

   <wsdl:message name="getKeywordsResponse">
      <wsdl:part name="getKeywordsReturn" type="impl:ArrayOf_xsd_anyType"/>
</wsdl:message>

每个消息的名称是方法名加上 RequestResponse。每个消息有一个嵌套的 <wsdl:part> 元素,它定义一个参数名和类型(请求的参数是字符串标题,响应的参数是一个命名的数组)。这使程序员或代码生成工具可以查明对 getKeywords() 的请求是什么样的,以及会返回什么。

如果不发送参数或没有返回值,就没有 <wsdl:part> 子元素:

清单 15. 没有 <wsdl:part> 子元素
<wsdl:message name="addKeywordResponse">
   </wsdl:message>

addKeyword() 方法没有返回值,所以它由一个空的 addKeywordResponse() 元素表示。

服务由端口类型表示

指定了消息之后,WSDL 现在可以通过 <wsdl:portType> 元素描述整个 Web 服务,见清单 16:

清单 16. <wsdl:portType> 元素
<wsdl:portType name="BookSearcher">
      <wsdl:operation name="setBooks" parameterOrder="books">
         <wsdl:input message="impl:setBooksRequest" name="setBooksRequest"/>
         <wsdl:output message="impl:setBooksResponse" name="setBooksResponse"/>
      </wsdl:operation>
      <wsdl:operation name="addBook" parameterOrder="title keywords">
         <wsdl:input message="impl:addBookRequest" name="addBookRequest"/>
         <wsdl:output message="impl:addBookResponse" name="addBookResponse"/>
      </wsdl:operation>
      <wsdl:operation name="addKeyword" parameterOrder="title keyword">
         <wsdl:input message="impl:addKeywordRequest" name="addKeywordRequest"/>
         <wsdl:output message="impl:addKeywordResponse" name="addKeywordResponse"/>
      </wsdl:operation>
      <wsdl:operation name="getKeywords" parameterOrder="title">
         <wsdl:input message="impl:getKeywordsRequest" name="getKeywordsRequest"/>
         <wsdl:output message="impl:getKeywordsResponse" name="getKeywordsResponse"/>
      </wsdl:operation>
      <wsdl:operation name="search" parameterOrder="keyword">
         <wsdl:input message="impl:searchRequest" name="searchRequest"/>
         <wsdl:output message="impl:searchResponse" name="searchResponse"/>
      </wsdl:operation>
</wsdl:portType>

每个操作映射到一个方法,各种输入和输出消息被连接到每个操作。还指定了参数的次序。这些信息完整地描述一个公开的方法,了解了它们的意义,就可以完全掌握方法的使用。

WSDL 提供一些与 SOAP 相关的低层信息

WSDL 已经提供了程序员需要的所有信息,但是还需要处理一些编码和与 SOAP 相关的细节。这些信息包含在 <wsdl:binding> 元素中,这些元素映射到已经定义的端口类型。大多数 <wsdl:binding> 处理编码和名称空间。例如,清单 17 定义与处理 getKeywords() 方法和操作相关的 SOAP 信息:

清单 17. 与处理 getKeywords() 相关的 SOAP 信息
<wsdl:operation name="getKeywords">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="getKeywordsRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://DefaultNamespace" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="getKeywordsResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
                           namespace="http://localhost:8080/axis/BookSearcher.jws" 
                           use="encoded"/>
         </wsdl:output>
</wsdl:operation>

WSDL 供谁使用?

WSDL 有双重作用:

  • WSDL 让 Web 服务和代码生成工具了解连接服务所需的语义和 SOAP 细节。
  • WSDL 让程序员了解可用的方法、这些方法需要的数据和返回的值。

JAX-RPC 和 Axis 框架提供的工具可以读取 WSDL,然后构建和连接那些使用 RPC 服务的代码。因此,WSDL 是支持代码的固有部分,我们不必手工处理 SOAP 编码语义。但是,WSDL 仍然很重要,尤其是在不掌握服务源代码的情况下。通过 WSDL 查明要发送什么以及会返回什么。

现在,您已经知道 WSDL 会提供关于每个服务方法和方法返回类型的信息。下面利用这些知识使用和连接 Web 服务。


构建客户机来访问 Web 服务

到目前为止,我们用了很多篇幅讨论 JAX-RPC,但是还没有实际使用这个 API。即使在自动部署 BookSearcher 类时,与 JAX-RPC 相关的工作也是由 Axis 完成的。现在,要让 JAX-RPC 发挥作用了。构建了希望访问的 Web 服务之后,需要编写客户机来使用 Web 服务。

更新类路径

在开始编写代码之前,需要修改类路径。在前面,已经把几个 JAR 文件放在 servlet 引擎的 lib/ 目录中,并使用 Axis 检验 JSP 确认这些 JAR 的位置是正确的。因为为了运行 Web 服务,servlet 引擎需要 JAX-RPC 和 Axis 类及其依赖项。

对于 Web 服务客户机也是如此。当然,可以重复相同的步骤,把相同的 JAR 文件放在 JDK 或 JRE 的 ext/lib 目录中。但是,这样做会弄乱 Java 系统并导致版本问题,还会把在您的机器上运行 Java 的其他人弄糊涂。更好的方法是更新 CLASSPATH 变量并设置配置文件或环境,使修改只对您的个人用户设置生效。

添加 JAX-RPC 和 Axis JAR

首先,进入 Axis 安装目录,看看 lib 目录。应该会看到与清单 18 相似的结果:

清单 18. Apache Axis 的 lib/ 目录中的 JAR
[bdm0509:/usr/local/java/axis-1_4] ls lib/
axis-ant.jar         log4j-1.2.8.jar
axis.jar         log4j.properties
commons-discovery-0.2.jar   saaj.jar
commons-logging-1.0.4.jar   wsdl4j-1.5.1.jar
jaxrpc.jar

把所有这些 JAR 文件添加到类路径中。惟一的可选文件是 axis-ant.jar,如果打算用 Ant 构建项目,就应该添加这个 JAR;它包含与 Ant 相关的扩展,支持在 Ant 构建文件中添加 Axis 任务。

添加这些 JAR 的最佳方法之一是,在 Windows 系统中设置环境变量,或者在 Mac OS X 或 Linux 环境中使用 .profile(或 .bashrc)。清单 19 给出我的 .profile 的一部分,它定位 Axis 安装目录,然后把其中的几个 JAR 添加到类路径中:

清单 19. 这个 .profile 把 Axis JAR 添加到 CLASSPATH 环境变量中
export JAVA_BASE=/usr/local/java
export JAVA_HOME=/Library/Java/Home
export XALAN_HOME=$JAVA_BASE/xalan-j_2_7_1
export AXIS_HOME=$JAVA_BASE/axis-1_4

export EDITOR=vi
export CVS_RSH=ssh
export PS1="['whoami':\w] "

export CLASSPATH=$XALAN_HOME/xalan.jar:
                 $XALAN_HOME/xml-apis.jar:
                 $XALAN_HOME/xercesImpl.jar:
                 $XALAN_HOME/xalan.jar:
                 $XALAN_HOME/xsltc.jar:
                 $XALAN_HOME/serializer.jar:
                 $AXIS_HOME/lib/axis.jar:
                 $AXIS_HOME/lib/jaxrpc.jar:
                 $AXIS_HOME/lib/commons-logging-1.0.4.jar:
                 $AXIS_HOME/lib/commons-discovery-0.2.jar:
                 $AXIS_HOME/lib/saaj.jar:
                 $AXIS_HOME/lib/wsdl4j-1.5.1.jar:
                 .

清单 19 中的硬换行只是为调整格式添加的。在实际的 .profile 文件中,所有类路径项都在一行上。

添加可选的 JAR

然后,还可能希望添加另外两项。在运行检验 JSP 时,Axis 曾经报告了几个可选项,也就是 activation.jar 和 mail.jar。如果按照前面的说明操作,可能已经下载了这两个 JAR 并把它们添加到 servlet 引擎的 lib/ 目录中。也应该把它们添加到类路径中。支持这些实用程序的服务器只能与也支持它们的客户机交互,所以这是一个关键步骤。

可以引用 servlet 引擎中的 JAR(如果在同一台机器上运行客户机代码和 servlet 引擎),也可以把这两个 JAR 重新下载或复制到客户机机器中,并把它们添加到类路径中。清单 20 给出所需的代码:

清单 20. 在类路径中添加邮件和激活 JAR
export JAVA_BASE=/usr/local/java
export JAVA_HOME=/Library/Java/Home
export XALAN_HOME=$JAVA_BASE/xalan-j_2_7_1
export AXIS_HOME=$JAVA_BASE/axis-1_4
export TOMCAT_HOME=$JAVA_BASE/apache-tomcat-6.0.16

export EDITOR=vi
export CVS_RSH=ssh
export PS1="['whoami':\w] "

export CLASSPATH=$XALAN_HOME/xalan.jar:
                 $XALAN_HOME/xml-apis.jar:
                 $XALAN_HOME/xercesImpl.jar:
                 $XALAN_HOME/xalan.jar:
                 $XALAN_HOME/xsltc.jar:
                 $XALAN_HOME/serializer.jar:
                 $AXIS_HOME/lib/axis.jar:
                 $AXIS_HOME/lib/jaxrpc.jar:
                 $AXIS_HOME/lib/commons-logging-1.0.4.jar:
                 $AXIS_HOME/lib/commons-discovery-0.2.jar:
                 $AXIS_HOME/lib/saaj.jar:
                 $AXIS_HOME/lib/wsdl4j-1.5.1.jar:
                 $TOMCAT_HOME/lib/activation.jar:
                 $TOMCAT_HOME/lib/mail.jar:
                 .

一定要确保应用修改,或对 .profile 执行 source,或重新启动终端应用程序。可以用 echo $CLASSPATH 命令检查类路径变量。应该会看到刚才添加的所有 JAR。

构建客户机类

现在可以开始构建连接 Web 服务的类。首先编写一个简单的骨架;它应该是一个 Java 类,它不需要实现特定的接口或扩展另一个类。实际上,客户机类中没有与 RPC 相关的内容。

清单 21 给出一个简单的 BookSearcherClient 骨架。这个类通过命令行获取一个关键字,然后调用 Web 服务中的方法。当然,这些方法目前仅仅是占位方法,稍后会编写它们的逻辑。

清单 21. Web 服务客户机类骨架
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.rpc.ServiceException;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;

public class BookSearcherClient {

  public static final String SERVICE_URL =
    "http://localhost:8080/axis/BookSearcher.jws";

  public BookSearcherClient() { }

  public Object[] search(String keyword) throws IOException {
    // placeholder
    return new Object[];
  }

  public static void main(String[] args) throws IOException {
    if (args.length != 1) {
      System.err.println("Usage: java BookSearcherClient [search keyword]");
      return;
    }
    String keyword = args[0];
    BookSearcherClient client = new BookSearcherClient();
    Object[] results = client.search(keyword);
    System.out.println("Returned books for keyword '" + keyword + "':");
    for (int i = 0; i<results.length; i++) {
      System.out.println("  " + results[i]);
    }
  }
}

清单 21 首先导入需要的所有类。这样就不必分散地添加这些类,这个过程单调乏味而且容易产生错误。几个类与 URL 连接相关,在连接 Web 服务时需要这些类。另外四个类(两个在 javax.xml 中,两个在 org.apache.axis.client 中)与 RPC 相关。后面会详细讨论每个类。

清单 21 还包含一个常量,这是要连接的 Web 服务的 URL。如果您使用不同的主机名或端口,就需要修改这个 URL,使它与自己的 servlet 引擎和 BookSearcher Web 服务路径匹配。URL 可能类似于 http://dev.myDomain.com/apps/BookSearcher.jws。可以使用不同的 URL,只要在浏览器中输入这个 URL 时能够获得与 图 7 相似的响应即可。

接下来是一个空的构造函数和一个根据关键字进行搜索的方法。稍后将详细讨论这个方法,所以暂时不必讨论这个方法为什么返回 Object[] (一个对象数组)。但是,目前这个方法只返回 null,这样就可以编译这个类。这个方法包含大部分 RPC 客户机代码。

最后一段代码(类的 main() 方法)创建一个新对象,从命令行获取一个关键字,并把它发送给 search() 方法,这个方法进而向 Web 服务发出请求。然后,构建客户机代码,向 Web 服务发出请求。

创建一个 ServiceCall 对象

JAX-RPC 客户机代码的两个基本对象是 org.apache.axis.client.Callorg.apache.axis.client.Service。这两个对象是 JAX-RPC javax.xml.rpc.Calljavax.xml.rpc.Service 类的实现。Apache Axis 提供了这些对象,其他 RPC 框架也提供自己的实现。

在任何 RPC 客户机中,第一步都是创建 Service 的新实例。然后,从这个实例创建 Call 的新实例,这样就可以调用服务了。清单 22 给出应该在 BookSearcherClient 类中添加的代码:几个新的成员变量和 search() 中的几行:

清单 22. 创建 ServiceCall 实现
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.rpc.ServiceException;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;

public class BookSearcherClient {

  public static final String SERVICE_URL =
    "http://localhost:8080/axis/BookSearcher.jws";

  private Service service;
  private Call call;
  private URL serviceUrl;

  public BookSearcherClient() { }

  public Object[] search(String keyword) throws IOException {
    try {
      if (service == null) {
        service = new Service();
      }
      if (call == null) {
        call = (Call)service.createCall();
      }

      // placeholder
      return new Object[];
    } catch (ServiceException e) {
      throw new IOException("Error creating service call: " + e.getMessage());
    }
  }

  public static void main(String[] args) throws IOException {
    if (args.length != 1) {
      System.err.println("Usage: java BookSearcherClient [search keyword]");
      return;
    }
    String keyword = args[0];
    BookSearcherClient client = new BookSearcherClient();
    Object[] results = client.search(keyword);
    System.out.println("Returned books for keyword '" + keyword + "':");
    for (int i = 0; i<results.length; i++) {
      System.out.println("  " + results[i]);
    }
  }
}

清单 22 中的代码创建一个新的 Service 实现,然后以这个类作为工厂创建 Call 实现的新实例(这与使用 JAXP 等框架时编写的代码非常相似,您应该很熟悉)。

在更健壮的实现中,可能用一个工厂类创建 Service 实现,使 Apache Axis 代码根本不使用这个类(至少不直接引用)。对于 Call 的 Apache Axis 实现也是如此。但是,为了简单,这个示例直接使用这些 Axis 类。

有了 Call 对象,就可以做一些真正的 RPC 工作了。

指定要连接什么

在调用服务之前,需要配置 Call 对象。每个 Call 通常连接一个特定的服务 URL(按照 SOAP 和 RPC 术语,这是目标端点)和操作。如果想改变端点 URL 或操作,就要重新配置 Call(或创建新实例)。

首先,创建一个 Java URL 对象来存储目标端点(发布 Web 服务的可通过 Web 访问的 URL):

serviceUrl = new URL(SERVICE_URL);

接下来,使用创建的 serviceURL 作为 Call 对象的 setTargetEndpointAddress() 方法的输入参数:

call.setTargetEndpointAddress(serviceUrl);

现在,Call 知道了要连接哪个服务。但是,还需要指定要调用的操作。这需要使用 setOperationName() 方法,而且不能只传递简单的字符串。相反,必须传递一个 QName。但是,可以动态地创建一个 QName,表示要使用 SOAP 编码,然后提供要调用的操作的字符串名:

call.setOperationName(new QName("http://soapinterop.org/", "search"));

这行代码写起来容易,但是不容易理解。QName 仅仅是一种表示限定名的方法,可以向 Java 平台提供与 XML 相似的数据,让 Java 平台替您执行一些幕后处理。对于操作,要牢记 WSDL(一种 XML 变体)用来表示每个操作。这是因为 Web 服务使用的 SOAP 传输协议以 XML 作为数据格式。所以,不能只使用操作的 Java 字符串名;需要一个 QName。但是,在 javax.xml.namespace.QName 类的帮助下,这很容易实现。清单 23 给出 search() 方法中需要的代码(请记住,前面已经添加了所有导入语句):

清单 23. 为服务调用设置端点和操作
public Object[] search(String keyword) throws IOException {
  try {
    if (service == null) {
      service = new Service();
    }
    if (call == null) {
      call = (Call)service.createCall();
    }
    if (serviceUrl == null) {
      serviceUrl = new URL(SERVICE_URL);
    }
    call.setTargetEndpointAddress(serviceUrl);

    // Select operation to call
    call.setOperationName(new QName("http://soapinterop.org/",
                          "search"));

    // placeholder
    return new Object[];

  } catch (MalformedURLException e) {
    throw new IOException("Error creating service URL at " + SERVICE_URL);
  } catch (ServiceException e) {
    throw new IOException("Error creating service call: " + e.getMessage());
  }
}

获得结果

下面就要执行实际调用(使用 Call 对象)并获得结果。这要用 invoke() 方法来完成,这个方法以一个对象数组作为参数:(Object[])。按照次序把参数放在这个数组中。对于 search() 方法,只有一个参数(关键字),所以像下面这样创建对象数组:

Object[] params = new Object[] { keyword };

如果要传递多个参数,比如在 addBook() 方法中,就要使用下面这样的代码:

Object[] params = new Object[] { bookTitle, keywordList };

然后,把这些参数传递给 invoke() 方法。请记住,不需要 指定要调用的服务的操作名(方法名);这已经通过 setOperationName() 方法完成了。下面需要获得结果并处理结果。

invoke() 方法返回一个 Object,但是这仅仅是实际返回类型的包装器。对于返回字符串的方法,这个对象包装一个 String;只需把返回的对象转换为正确的类型:

Object[] results = (Object[])call.invoke(new Object[] { keyword });

在这个示例中,服务上的方法返回一个 List。但是,SOAP 并不理解列表,所以它默认使用一种更泛化的类型:对象数组。这增加了程序员的负担,因为程序员必须亲自处理这些泛型对象并确保类型安全。这也是泛型和参数化目前在 Web 服务中用处不大的原因之一;在把信息从客户机传递给服务器的过程中,会丧失类型安全性。

把这行代码放到 search() 方法中,见清单 24:

清单 24. 执行调用并返回结果
public Object[] search(String keyword) throws IOException {
  try {
    if (service == null) {
      service = new Service();
    }
    if (call == null) {
      call = (Call)service.createCall();
    }
    if (serviceUrl == null) {
      serviceUrl = new URL(SERVICE_URL);
    }
    call.setTargetEndpointAddress(serviceUrl);

    // Select operation to call
    call.setOperationName(new QName("http://soapinterop.org/",
                          "search"));

    // Make call and get result
    Object[] results = (Object[])call.invoke(new Object[] { keyword });

    return results;
  } catch (MalformedURLException e) {
    throw new IOException("Error creating service URL at " + SERVICE_URL);
  } catch (ServiceException e) {
    throw new IOException("Error creating service call: " + e.getMessage());
  }
}

与设置端点和操作的代码一样,这里的代码也很简单,但是它会完成许多操作:

  1. Call 实例查找请求要发送到的 URL 端点。
  2. Call 实例查找要调用的操作。
  3. 使用目标端点和操作名以及您提供的参数,构造一个新的 SOAP 请求。这个请求采用 XML 格式。
  4. 把 XML 格式的 SOAP 请求发送给 Web 服务。
  5. Web 服务用一个 XML 格式的响应来回复这个请求。
  6. 把生成的 XML 转换为 Java 对象(在这里是一个对象数组)并返回给程序。

当然,程序员只需调用 invoke() 方法即可。

现在,程序完成了,重新编译它并确保 Web 服务和 servlet 引擎正在运行。然后,运行客户机程序;应该会得到与清单 25 相似的结果:

清单 25. 运行 BookSearcher Web 服务客户机程序
[bdm0509:~/Documents/developerworks/jax-rpc] java BookSearcherClient design
Returned books for keyword 'design':
  Emotional Design
  Indexed

这个结果没有多少新奇的地方;实际上,它与 清单 7 中测试程序的结果(清单 8)非常接近。主要的区别并不 明显:所有请求和响应都使用 SOAP、XML 格式和 RPC 模型。您只需编写非常简单明了的 Java 代码。


结束语

利用本教程的最佳方法是把学到的知识应用于自己要解决的问题。教程中的示例都是虚构的,只用来解释关键的概念。您应该选择一个问题并应用 JAX-RPC 来解决它。您可以构建一个基于 RPC 的服务,用它响应来自 Web 页面的 Ajax 请求。Java 程序中可以通过从服务器获取信息受益,并且 RPC 是响应 Java 程序请求的好方法。还可以把使用 SOAP 的现有代码改为使用 JAX-RPC。在一两个真实的应用程序中应用 JAX-RPC,无论什么应用程序。您肯定会遇到本教程没有详细讨论的问题,在解决这些问题的过程中,您会巩固在这里学到的知识。

JAX-RPC(以及一些最出色的 Web 服务工具集和 API)的优点之一是,API 非常宽松。换句话说,JAX-RPC 并没有对发送或接收的数据施加太多限制。只要使用与语言相关的格式并正确地设置客户机和服务器,JAX-RPC 就能够发挥作用。这大大减少了对编程的干扰;您只需设置好 JAX-RPC 组件,然后就可以编写自己的业务逻辑、应用程序逻辑等等。不必担心 JAX-RPC 会影响您的代码。

由于这些原因,JAX-RPC 是很不错的 Web 服务技术。它使我们能够对特定类型的服务器端程序进行特定类型的调用。如果需要特定的客户机-服务器关系,就使用 JAX-RPC。如果不需要,就不使用。换句话说,您可以自由地决定是否使用 JAX-RPC;与此相反,选择 Java 作为编程语言或选择 Eclipse 作为开发环境,就会丧失一定的自由度。您不必局限于 JAX-RPC,也不会被迫改变编程标准或实践。实际上,JAX-RPC 是实现 Web 服务和客户机-服务器交互的几种最佳技术之一。

因此,您应该用 JAX-RPC 构建几个程序,尽可能掌握 JAX-RPC 技术。当需要向部门、组织或公司外的消费者提供简单的 Web 服务时,JAX-RPC 是不错的选择。它符合 SOAP 和 WSDL 规范,大多数 Web 服务消费者都能够处理它。如果您要应付的局面没这么简单,可以选用其他技术,包括新的 JAX-WS。总之,多掌握一种工具,就多一种选择,更容易找到最适合您的 应用程序的解决方案。


下载

描述名字大小
示例代码j-jaxrpc_code.zip4KB

参考资料

学习

获得产品和技术

  • Apache Axis:本教程使用的工具集是 Axis 1.x,这可能是目前最简单的 JAX-RPC 项目。
  • Apache Tomcat:需要用一个 Web 服务器来驻留 JAX-RPC 应用程序,Tomcat 是最好最容易运行的 Web 服务器。
  • Project GlassFish:GlassFish 是一种功能齐全的引擎,支持用 Java 语言编写的基于 RPC 的 Web 服务,包括一个 JAX-RPC 参考实现。
  • Java and XML, Third Edition (Brett McLaughlin 和 Justin Edelson,O'Reilly Media,2006 年):这本书全面讨论了 XML 技术,包括关于 Java Web 服务的丰富信息。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, SOA and web services
ArticleID=328291
ArticleTitle=用 JAX-RPC 构建 RPC 服务和客户机
publish-date=08062008