用JAX-RPC构建RPC服务和客户机:使用JavaAPI构建基于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 接收器和一个简单的客户端应用程序。

开始之前

关于本教程

本教程完整地介绍如何安装、配置、构建和运行基于远程过程调用(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 正在运行

显然,如果安装了其他 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.encodingjavax.xml.rpc.handlerjavax.xml.rpc.handler.soapjavax.xml.rpc.holdersjavax.xml.rpc.serverjavax.xml.rpc.soapjavax.xml.rpc 包中的三个接口是核心组件:

javax.xml.rpc.Calljavax.xml.rpc.Servicejavax.xml.rpc.Stub

在本教程中,您将了解关于这些接口和其他 JAX-RPC 包的更多信息。目前要注意,这三个核心组件是 接口 而不是类。实际上,核心 JAX-RPC 包只包含很少几个具体类,其中的 NamespaceConstants 和 ParameterMode 实际上是实用程序类。那么,类(也就是用 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 最新版本

选择最新版本;本教程使用 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] lsapache-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] lsROOT   docs   host-manageraxis   examples  manager

启动 servlet 引擎

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

[bdm0509:/usr/local/java/apache-tomcat-6.0.16/bin] sh shutdown.shUsing CATALINA_BASE:  /usr/local/java/apache-tomcat-6.0.16Using CATALINA_HOME:  /usr/local/java/apache-tomcat-6.0.16Using CATALINA_TMPDIR: /usr/local/java/apache-tomcat-6.0.16/tempUsing JRE_HOME:    /Library/Java/Home[bdm0509:/usr/local/java/apache-tomcat-6.0.16/bin] sh startup.shUsing CATALINA_BASE:  /usr/local/java/apache-tomcat-6.0.16Using CATALINA_HOME:  /usr/local/java/apache-tomcat-6.0.16Using CATALINA_TMPDIR: /usr/local/java/apache-tomcat-6.0.16/tempUsing 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 页面

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

图 4. Apache Axis 的检验页面(包含几个错误)

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

缺少必需的 javax.activation.DataHandler 类。

缺少 javax.mail.internet.MimeMessage helper 类。

缺少 org.apache.xml.security.Init helper 类。

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

下载缺少的组件

得到缺少的组件的完整列表之后,应该下载所有这些组件。首先单击各个组件的链接。下载引用的每 个库,根据需要展开库,找到 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,所以如果得到与图 5 相似的结果,Axis 就安装好了。

测试一个简单的 Java Web 服务

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

图 6. Axis 附带一个示例 Java Web 服务

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

清单 2. 示例 Web 服务返回 XML 版本的请求头

          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   referer:http://localhost:8080/axis/       accept:text/xml,application/xml,      application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,      */*;q=0.5   accept-language:en-us   accept-encoding:gzip,  deflate   cookie:JSESSIONID=     42A37ED6763ECC773D5FEB70484D57B1   connection:keep-alive   host:localhost:8080      

这些头仍然有点儿混乱(其中有许多与 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 BookSearchTesterKeywords for 'Purple Cow':  marketing  business  commercials  consumersBooks 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 访问

还会看到 “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 描述可用的方法

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               

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

分析 WSDL 来理解服务

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

WSDL 包含大量名称空间信息

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

清单 11. 根元素声明

每个 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 的下一个关键部分包含在 元素中,见清单 12:

清单 12. 元素

                                                                                               

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

清单 13. 定义 RPC 客户机和服务可以理解的一个映射

                             

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

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

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

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

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

清单 14. WSDL 中的消息声明

             

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

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

清单 15. 没有 子元素

  

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

服务由端口类型表示

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

清单 16. 元素

                                                                                                   

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

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

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

清单 17. 与处理 getKeywords() 相关的 SOAP 信息

                                           

WSDL 供谁使用?

WSDL 有双重作用:

WSDL 让 Web 服务和代码生成工具了解连接服务所需的语义和 SOAP 细节。

WSDL 让程序员了解可用的方法、这些方法需要的数据和返回的值。

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

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

往事是尘封在记忆中的梦,而你是我唯一鲜明的记忆,

用JAX-RPC构建RPC服务和客户机:使用JavaAPI构建基于RPC的Web服务

相关文章:

你感兴趣的文章:

标签云: