Java Remote Method Invocation(Java远程方法调用)-- Java分布式计算白皮书


(来源:http://www.sun.com.cn)

概述
优点
传递属性
服务器定义的策略
计算服务器
代理
面向对象的代码重用与设计模式
与现有服务器的连接
JDBC --连接数据库
JNI --本机方法
体系结构
保密与安全
防火墙
RMI应用在演变的企业中
结论

概述

Java Remote Method Invocation ( RMI -- Java远程方法调用)允许 您使用Java编写分布式对象。本文将介绍RMI的优点以及 如何将其连接到现有的和原有的系统中,以及与用Java 编写的组件的连接。

RMI为采用Java对象的分布式计算提供了简单而直接的 途径。这些对象可以是新的Java对象,也可以是围绕现 有API的简单的Java包装程序。Java体现了“编写一次就 能在任何地方运行的模式。而RMI可将Java模式进行扩展 ,使之可在任何地方运行”。

因为RMI是以Java为核心的,所以,它将Java的安全性 和可移植性等强大功能带给了分布式计算。您可将代理 和梢?务逻辑等属性移动到网络中最合适的地方。如果您 要扩展Java在系统中的使用,RMI将使您充分利用其强大 功能。

RMI可利用标准Java本机方法接口JNI与现有的和原有的 系统相连接。RMI还可利用标准JDBC包与现有的关系数据 库连接。RMI/JNI和RMI/JDBC相结合,可帮助您利用RMI与目 前使用非Java语言的现有服务器进行通信,而且在您需 要时可扩展Java在这些服务器上的使用。RMI可帮助您在 扩展使用时充分利用Java的强大功能。

优点

从最基本的角度看,RMI是Java的远程过程调用(RPC)机 制。与传统的RPC系统相比,RMI具有若干优点,因为它 是Java面向对象方法的一部分。传统的RPC系统采用中性 语言,所以是最普通的系统--它们不能提供所有可能的 目标平台所具有的功能。

RMI以Java为核心,可与采用本机方法与现有系统相连 接。这就是说,RMI可采用自然、直接和功能全面的方式 为您提供分布式计算技术,而这种技术可帮助您以不断 递增和无缝的方式为整个系统添加Java功能。

RMI的主要优点如下:

  • 面向对象:RMI可将完整的对象作为参数和返回值进行传 递,而不仅仅是预定义的数据类型。也就是说,您可以 将类似Java哈希表这样的复杂类型作为一个参数进行传 递。而在目前的RPC系统中,您只能依靠客户机将此类对 象分解成基本数据类型,然后传递这些数据类型,最后 在服务器端重新创建哈希表。RMI则不需额外的客户程序 代码(将对象分解成基本数据类型),直接跨网传递对象 。
  • 可移动属性:RMI可将属性(类实现程序)从客户机移动 到服务器,或者从服务器移到客户机。例如,您可以定 义一个检查雇员开支报告的接口,以便察看雇员是否遵 守了公司目前实行的政策。在开支报告创建后,客户机 就会从服务器端获得实现该接口的对象。如果政策发生 变化,服务器端就会开始返回使用了新政策的该接口的 另一个实现程序。您不必在用户系统上安装任何新的软 件就能在客户端检查限制条件--从而向用户提供烁?快的 反馈,并降低服务器的工作量。这样就能具备最大的灵 活性,因为政策改变时只需要您编写一个新的Java类, 并将其在服务器主机上安装一次即可。
  • 设计方式:对象传递功能使您可以在分布式计算中充分 利用面向对象技术的强大功能,如二层和三层结构系统 。如果您能够传递属性,那么您就可以在您的解决方案 中使用面向对象的设计方式。所有面向对象的设计方式 无不依靠不同的属性来发挥功能,如果不能传递完整的 对象--包括实现和类型--就会失去设计方式上所提供的 优点。
  • 安全:RMI使用Java内置的安全机制保证下载执行程序时 用户系统的安全。RMI使用专门为保护系统免遭恶意小应 用程序侵害而设计的安全管理程序,可保护您的系统和 网络免遭潜在的恶意下载程序的破坏。在情况严重时, 服务器可拒绝下载任何执行程序。
  • 便于编写和使用:RMI使得Java远程服务程序和访问这些 服务程序的Java客户程序的编写工作变得轻松、简单。 远程接口实际上就是Java接口。服务程序大约用三行指 令宣布本身是服务程序,其它方面则与任何其它Java对 象类似。这种简单方法便于快速编写完整的分布式对象 系统的服务程序,并快速地制做软件的原型和早期版本 ,以便于进行测试和评估。因为RMI程序编写简单,所以 维护也简单。
  • 可连接现有/原有的系统:RMI可通过Java的本机方法接 口JNI与现有系统进行进行交互。利用RMI和JNI,您就能 用Java语言编写客户端程序,还能使用现有的服务器端 程序。在使用RMI/JNI与现有服务器连接时,您可以有选择 地用Java重新编写服务程序的任何部分,并使新的程序 充分发挥Java的功能。类似地,RMI可利用JDBC、在不修 改使用数据库的现有非Java源代码的前提下与现有关系 数据库进行交互。
  • 编写一次,到处运行:RMI是Java“编写一次,到处运行 ”方法的一部分。任何基于RMI的系统均可100%地移植到 任何Java虚拟机上,RMI/JDBC系统也不例外。如果使用RMI/JNI 与现有系统进行交互工作,则采用JNI编写的代码可与任 何Java虚拟机进行编译、运行。
  • 分布式垃圾收集:RMI采用其分布式垃圾收集功能收集不 再被网络中任何客户程序所引用的远程服务对象。与Java 虚拟机内部的垃圾收集类似,分布式垃圾收集功能允许 用户根据自己的需要定义服务器对象,并且明确这些对 象在不再被客户机引用时会被删除。
  • 并行计算:RMI采用多线程处理方法,可使您的服务器利 用这些Java线程更好地并行处理客户端的请求。
  • Java分布式计算解决方案:RMI从JDK 1.1开始就是Java平台 的核心部分,因此,它存在于任何一台1.1 Java虚拟机中 。所有RMI系统均采用相同的公开协议,所以,所有Java 系统均可直接相互对话,而不必事先对协议进行转换。
传递属性

前面我们讲到,RMI可以传递属性,并简单介绍了一下 一个有关开支报告程序的情况。下面我们将深入讨论如 何设计这样的系统。这样介绍的目的是使您能够利用RMI 的功能将属性从一个系统传递到另一个系统,并随心所 欲地安排当前的计算地点,并便于将来的改变。下面的 例子并未涉及真实世界可能发生的所有问题,但可帮助 读者了解处理问题的方法。

服务器定义的策略

Figure 1. An Expense Reporting Architecture

图1是可进行动态配置的开支报告系统的示意图。客 户机向用户显示图形用户界面(GUI),用户填写开支报告 。客户机程序使用RMI与服务器进行通信。服务器使用JDBC ( Java关系数据库连接包)将开支报告存储在数据库中。 至此,这看起来与其它多层次系统大同小异,但有一个 重大区别-- RMI能下载属性。

假定公司关于开支报告的政策发生改变。例如,目前 公司只要求对超过20美元的开支需开具发票。但到明天 ,公司认为这太宽松了,便决定除不超过20美元的餐费 以外,任何开支均需开具发票。如果不能下载属性的话 ,那么在设计便于修改的系统时您可选择下列方法之一 :

  • 用客户端安装与政策有关的程序。政策改变时,必须更 新包含此政策的所有客户端程序。您可在若干服务器上 安装客户程序,并要求所有用户从这些服务器之一运行 客户程序,从而减少问题。但这仍不能彻底解决问题-- 那些让程序运行好几天的用户就无法使程序更新,而总 是会有一些用户为了方便而把软件复制到本地磁盘上。
  • 您可要求服务器在每次向开支报告添加项目时检查政策 。但这样就会在客户机和服务器之间产生大量数据流, 并增加服务器的工作量。这还会使系统变得更加脆弱-- 网络故障会立即妨碍用户,而不仅仅是只在其呈交开支 报告或启动新的报告时对其产生影响。同时,添加项目 的速度也会变慢,因为这需要穿越整个网络往返一圈才 能到达(不堪重负的)服务器。
  • 您可在呈交报告时要求服务器对政策进行检查。这样就 会使用户创建很多必须待批报告的错误项目,而不是立 刻捕捉到第一个错误,从而使用户有机会停止制造错误 。为避免浪费时间,用户需要立刻得到有关错误的反馈 。
有了RMI,您就能以简单的方法调用程序从服务器得到属 性,从而提供了一种灵活的方式,将计算任务从服务器 卸载到客户机上,同时为用户提供速度更快的反馈。当 用户准备编写一份新的开支报告时,客户机就会向服务 器要求一个对象,该对象嵌入了适用于该开支报告的当 前政策,就如同通过用Java编写的政策接口所表示的那 样。该对象可以以任何方式实现当前政策。如果这是客 户机RMI首次看到这种专门执行的政策,就会要求服务器 提供一份执行过程的副本。如果执行过程将来发生变化 ,则一种新的政策对象将被返回给客户机,而RMI运行时 则会要求得到新的执行过程。

这表明,政策永远是动态的。您要想修改政策,就只 需简单地编写通用政策接口的新的执行程序,把它安装 在服务器上,并对服务器进行配置以返回这种新类型的 对象即可。这样,每台客户机都会根据新的政策对新的 开支报告进行检查。

这是一种比任何静态方法都更好的方法,原因如下:

  • 所有客户机不必暂停或用新的软件来升级--软件可根据 需要在不工作时加以更新。
  • 服务器不必参与项目检查工作,该工作可在本地完成。
  • 允许动态限制,因为对象执行程序(而不仅仅是数据)是 在客户机和服务器之间进行传递的。
  • 使用户能立刻看到错误。
使客户机在服务器上所能调用的方法的远程接口定义如 下:
import java.rmi.*;
public interface ExpenseServer extends Remote {
    Policy getPolicy() throws RemoteException;
    void submitReport(ExpenseReport report)
        throws RemoteException, InvalidReportException;
}
import语句输入Java RMI包。所有RMI类型均在包java.rmi或 其子包内定义。接口ExpenseServer是一般的Java接口,具有 如下两种有趣的特点:
  • 它扩展了名为Remote的RMI接口,这使该接口标记为可用 于远程调用。
  • 它的所有方法均抛出RemoteException,后者用来表示网络或 信息故障。远程方法还可抛出您所需要的任何其他例外 ,但至少必须抛出RemoteException,这样您才能处理只会在 分布式系统中发生的错误状态。该接口本身支持两个方 法:getPolicy (返回一个实现政策接口的对象),和submitReport (提交一个完成的开支请求,并在报告无论因何种原因 使表格出现错误时,抛出一个例外)。
政策接口本身可声明一种使客户机知道能否在开支报告 中添加一个项目的方法:
public interface Policy {
    void checkValid (Expenseentry entry)
        throws PolicyViolationException;
}
如果该项目有效--即符合当前政策,则该方法可正常返 回。否则就会抛出一个描述该错误的例外。政策接口是 本地的(而非远程的),所以将通过本机对象在客户机上 执行--在客户机的虚拟机上,而非整个网络上运行的对 象。客户机可能运行下列程序:
Policy curPolicy = server.getPolicy();
start a new expense report
show the GUI to the user
while (user keeps adding entries) {
    try {
        curPolicy.checkValid(entry);    // throws exception if not OK
        add the entry to the expense report
    } catch (PolicyViolationException e) {
        show the error to the user
    }
}
server.submitReport(report);
当用户请求客户机软件启动一份新的开支报告时,客户 机就调用server.getPolicy,并要求服务器返回一个包含当 前开支政策的对象。添加的每个项目首先都被提交给该 政策对象,以获得批准。如果政策对象报告无错误,则 该项目就被添加到报告中;否则错误就被显示给用户, 而后者可采取修正措施。当用户完成向报告中添加项目 时,整个报告就被呈交。服务程序如下:
import java.rmi. *;
import java.rmi.server. *;
class ExpenseServerImpl
    extends UnicastRemoteObject
    implements ExpenseServer
{
    ExpenseServerImpl() throws RemoteException {
        // . . . set up server state . . .
    }
    public Policy getPolicy() {
        return new TodaysPolicy();
    }
    public void submitReport(ExpenseReport report) {
        // . . . write the report into the db . . .
    }
}
除基本程序包外,我们还输入RMI的服务程序包。类型UnicastRemoteObject 定义了此服务程序远程对象的类型,在本例中,应为单 一服务程序而非复制服务(下面还会详细介绍)。Java类 ExpenseSeverImpl实现远程接口ExpenseServer的方法。远程主机 的客户机可使用RMI将信息发送给ExpenseServerImpl对象。

本文中讨论的重要方法是getPolicy,它简单地返回定义 当前政策的对象。下面看一个执行政策的例子:

public class TodaysPolicy implements Policy {
    public void checkValid(ExpenseEntry entry)
        throws PolicyViolationException
    {
        if (entry.dollars() < 20) {
            return;    // no receipt required
        else if (entry.haveReceipt() == false) {
            throw new PolicyViolationException;
        }
    }
}
TodaysPolicy进行检查的目的是确保无收据的任何项目均少 于20美元。如果将来政策发生变化,仅少于20美元的餐 费可不受“需要收据”政策的限制,则您可提供新的政 策实现:
public class TomorrowsPolicy implements Policy {
    public void checkValid(ExpenseEntry entry)
        throws PolicyViolationException
    {
        if (entry.isMeal() && entry.dollars() < 20) {
            return;    // no receipt required
        } else if (entry.haveReceipt() == false) {
            throw new PolicyViolationException;
        }
    }
}
编写这个类,并把它安装在服务器上,然后告诉服务器 开始提供TomorrowsPolicy对象,而非TodaysPolicy对象,这样 您的整个系统就会开始使用新的政策。当客户机调用服 务器的getPolicy方法时,客户机的RMI就会检查返回的对 象是否为已知类型。每台客户机首次遇到TomorrowsPolicy时 ,RMI就会在getPolicy返回前下载政策的实现。客户机可 轻松地开始增强这个新的政策。

RMI使用标准Java对象序列化机制传递对象。引用远程 对象参数作为远程引用传递。如果向某方法提供的参数 为原始类型或本机(非远程)对象,则向服务器传递一个 深副本。返回值也拾?照同样的方式处理,只不过是沿其 它方向。RMI可使用户向本机对象传递和返回完整对象图 并为远程对象传递和返回引用。

在真实的系统中,getPolicy方法可能会有一个可以识别 用户及开支报告类型(差旅、客户关系等)的参数,这样 可使政策加以区别。您或者可以不要求单独的政策和开 支报告对象,但您可以有一种newExpenseReport方法,它可返 回一个直接检查政策的ExpenseReport对象。这最后一种策略 可使您像修改政策一样简单地修改开支报告的内容--当 公司决定需要把餐费划分为早餐、午餐和晚餐项目,而 且像上述修改政策一样简单地执行修改时--可编写一个 实现该报告的新类,客户程序就会自动使用这个类。

计算服务器

开支报告的例子表示了客户机如何从服务器得到属性 。属性可沿两个方向传递--客户机也可将新的类型传递 给用户。最简单的例子就是如图2所示的计算服务器, 该服务程序可执行任意任务,这样整个企业内的客户机 都能利用高端或专用计算机。

Figure 2. An Compute Server Architecture

任务由一个简单的本地(非远程)接口定义:
public interface Task {
   Object run();
}
运行时,它就会进行一些计算,并返回一个包含结果的 对象。这完全是一般性的任务--几乎所有计算任务都可 在这个接口下实现。远程接口ComputeServer也同样简单:
import java.rmi.*;
public interface ComputeServer extends Remote {
  Object compute(Task task) throws RemoteException;
}
这个远程接口的唯一目的就是使客户机创建一个Task (任 务)对象,并把它发送给服务器执行,最后返回结果。 该服务器的基本实现如下:
import java.rmi.*;
import java.rmi.server.*;
public class ComputeServerImpl
   extends UnicastRemoteObject
   implements ComputeServer
{
   public ComputeServerImpl() throws RemoteException { }
   public Object compute(Task task) {
       return task.run();
   }
   public static void main(String[] args) throws Exception {
       // use the default, restrictive security manager
       System.setSecurityManager(new RMISecurityManager());
       ComputeServerImpl server = new ComputeServerImpl();
       Naming.rebind("ComputeServer", server);
       System.out.println("Ready to receive tasks");
       return;
   }
}
如果您看一看compute方法就会发现,它非常简单。它只 是接受传递给它的对象,并返回计算的结果。main方法 包括服务器的启动代码--它安装了RMI的缺省安全管理程 序,以防止他人存取本地系统,并创建可处理进入的请 求的ComputeServerImpl对象,并将其与名字"ComputeServer"关联 。这时,服务器已经准备好接收要执行的任务,而main 也完成了其设置。

如上所述,这实际上是一种全面和实用的服务。系统 可以得到改进,比如,可添加要计算的参数,从而对使 用服务程序的部门进行计费。但在很多情况下,上述接 口和及其实现允许使用高端计算机进行远程计算。这又?明 了RMI的简单性--如果您键入上述类,对其进行编译,并 启动服务程序,您就拥有了能执行任意任务的运行计算 服务器。

下面介绍一个使用这种计算服务的例子。假定您购买 了一个能运行大量计算操作应用程序的非常高端的系统 。管理员可在该系统上启动一个Java虚拟机,运行ComputeServerImpl 对象。该对象现在就可接受要运行的任务。

现在假定一个小组准备通过一组数据培训一个神经网 络,以帮助制订采购策略。他们可以采用的步骤如下:

  • 定义一个类--暂且称之为PurchaseNet。它能接受一组数据 ,并运行培训数据,返回一个经过培训的神经网络。PurchaseNet 将实现Task (任务)接口,并在其run方法中执行其工作。 他们可能还需要一个Neuron类来描述所返回的网络中的节 点,而且很可能需要其它类来描述处理过程。run方法将 返回一个由一组经过培训的Neuron对象组成的NeuralNet对 象。
  • 当这些类被编写好并进行小规模测试时,用一个PurchaseNet 对象调用ComputeServer的compute方法。
  • 当ComputeServerImpl对象中的RMI系统接收到作为进入参数的 PurchaseNet对象时,它就下载PurchaseNet的实现,并调用该 服务器的compute方法,并把该对象作为Task (任务)参数 。
  • Task,也就是PurchaseNet对象,将开始执行其执行程序。 当执行程序需要诸如Neuron和NeuralNet等新的类时,它们 可根据需要下载。
  • 所有计算都将在计算服务器上执行,而客户机线程则等 待结果。(客户机系统上的另一个线程则会显示“正在 等待”光标或使用Java的内置并行机制执行另一个任务 )。当运行返回其NeuralNet对象时,这个对象将作为compute 方法的结果被传递回客户机。
这不需要在服务器上安装其它软件--所有必须完成的任 务都由各部门在其自己的计算机上完成,随后再根据需 要传递给计算服务器主机。

这个简单的计算服务器体系结构为系统的分布式功能 提供了功能强大的转换能力。一项任务的计算可以被转 移到一个能为其提供最好支持的系统上完成。这样的系 统可以被用来:

  • 在ComputeServerImpl对象运行于有数据挖掘需要的主机上, 支持数据挖掘应用程序。这样可使您轻松地把任何计算 移动到数据所在的地方。
  • 在从当前股票价格、发货信息或其它实时信息等外部资 源获得直接数据的服务器上运行计算任务。
  • 通过运行ComputeServer (接受进入的请求并将其转送到运行 ComputeServerImpl的负担最小的服务器上)的不同实现,而 将任务分布在多个服务器上。
代理

因为RMI允许使用Java实现下载属性,所以您可使用RMI 编写代理系统。代理的最简单格式如下:

import java.rmi.*;
public interface AgentServer extends Remote {
    void accept(Agent agent)
        throws RemoteException, InvalidAgentException;
}
public interface Agent extends java.io.Serializable {
    void run();
}
启动一个代理也就创建了实现Agent (代理)接口、找到服 务器、激活接受该代理对象的类。该代理的执行程序将 被下载到服务器上运行。accept方法将启动一个该代理的 新线程,激活其run方法,然后返回,从而使该代理一直 执行到run方法返回为止。代理可通过激活在另一台主机 上运行的服务程序的accept方法而移植到该主机,而其本 身则作为将被接受的代理来传递,并结束其在原来主机 上的线程。

面向对象的代码重用与设计 模式

面向对象的编程是一项允许代码重用的强大技术。很 多企业组织都使用面向对象的编程来减轻创建程序的负 担和提高系统的灵活性。RMI是完全面向对象的--信息被 发送给远程对象,而且对象可以被传递和返回。

Design Patterns (设计模式)目前在描述面向对象设计的 实践活动中获得了相当大的成功。首先是因为Design Patterns 的创新工作而使之大受欢迎,这些编程方式是一种正式 描述解决某类特定问题的完整方法的途径。所有这些设 计模式都依赖于创建一个或多个抽象概念,这些抽象概 念允许不同的实现,从而允许和增强软件重用。软件重 用是面向对象技术的主要优势,而设计模式则是促进重 用的最受欢迎的技术之一。

所有设计模式都依赖面向对象的多态性--这是对象( 如Task )拥有多个实现的能力。算法的普通部分(如compute 方法)不必知道使用了哪个实现,它只需知道得到一个 对象后应该对该对象采取什么操作。特别地,计算服务 器就是Command (指令)模式的一个例子,它可使您将请求 (任务)表示为对象,并对其进行调度。

只有当包括执行程序在内的完整对象能在客户机和服 务器之间传递时,才会存在这样的多态性。DCE和DCOM等 传统的RPC系统以及CORBA等基于对象的RPC系统不能下载 并执行程序,因为它们不能把真实对象作为参数传递, 而只能传递数据。

RMI可传递包括执行程序在内的所有类型,所以您可以 在分布式计算解决方案中,而不仅仅是本地计算中使用 面向对象的编程--包括设计方式。如果没有RMI这样完全 面向对象的系统,那么您就必须放弃很多分布式计算系 统中的设计方式--以及面向对象的软件重用的其它形式 。

与现有服务器的连接

人们常说,RMI主要是“从Java到Java”,但这种说法 掩盖了这样一个事实:Java可使用被称为JNI的本机方法 接口,很容易地与现有和原有系统连接。JNI和RMI的混 合使用与任何其它Java程序一样简单。您可使用JDBC,再 结合RMI,与现有的关系数据库连接。也就是说,您可使 用RMI连接二层次和三层次系统--即使双方都不是用Java 编写的亦可。这样做有很大的好处和优势,下面会详细 阐述。但首先让我们看一看它是如何完成的。

假定您现在有一台在关系数据库中存储了有关客户订 单信息的服务器。在任何多层次系统中,您都得设计一 个远程接口,以便于客户机访问服务器--利用作为Remote 接口的RMI:

import java.rmi.*;
import java.sql.SQLException;
import java.util.Vector;
public interface OrderServer extends Remote {
    Vector getUnpaid() throws RemoteException, SQLException;
    void shutDown() throws RemoteException;
    // ... other methods (getOrderNumber, getShipped, ...)
}
java.sql包包含JDBC包。每个远程方法均可被服务器采用实 际数据库的JDBC调用,或通过采用其它数据库访问机制 的本机方法实现。上面所示的方法返回一个Order (订单) 对象的Vector (列表)。Order (订单)就是在您的系统中定 义的、用来保存客户订单的类。

本节将介绍如何使用JDBC实现getUnpaid,和如何使用JNI 实施shutDown。

JDBC -- 连接数据库

使用JDBC实现getUnpaid的OrderServerImpl如下:

import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.Vector;
public class OrderServerImpl
    extends UnicastRemoteObject
    implements OrderServer
{
    Connection db;                     // connection to the db
    PreparedStatement unpaidQuery;     // unpaid order query
    OrderServerImpl() throws RemoteException, SQLException {
        db = DriverManager.getConnection("jdbc:odbc:orders");
        unpaidQuery = db.preparedStatement("…");
    }
    public Vector getUnpaid() throws SQLException {
        ResultSet results = unpaidQuery.executeQuery();
        Vector list = new Vector();
        while (results.next())
            list.addElement(new Order(results));
        return list;
    }
    public native void shutDown();
}
其中大多是JDBC任务。除了以Order开始的类型是您的系 统的一部分类型以外,您所看到的所有类型均为JDBC或 RMI的一部分。构造函数初始化OrderServerImpl对象,创建与 jdbc URL中所规定的数据库的连接( Connection)。有了这个 连接,我们就可以使用prepareStatement定义一个能找到所 有未付款订单的查询。在此还可为其它方法定义其它查 询。OrderServerImpl作为数据库在同一个系统上运行,而且 还可能是在同一个进程中(下面将讨论shutDown )。

当getUnpaid方法在RMI服务器对象OrderServerImpl上被调用 后,就会执行预先编译的查询,并返回包含所有匹配元 素的JDBC ResultSet对象。随后,我们为结果集中的每个项 目创建新的Order对象,并将其添加到Vector对象中( Java 的动态数组)。在结束读取结果后,我们将这个向量返 回给客户机,后者可将结果显示给用户,或者其他相关 的人。

JNI -- 本机方法

RMI服务器和客户机可利用本机方法与现有的和原有的 系统连接。您可使用本机方法实现不能直接访问数据库 的远程方法,或者通过采用现有代码更简单地实现。您 可使用本机接口JNI编写C和C++程序,以实现?Java方法并 Java对象上调用该方法。用本机方法实现shutDown的程序 如下:

JNIEXPORT void JNICALL
Java_OrderServerImpl_shutDown(JNIEnv *env, jobject this)
{
    jclass cls;
    jfieldID fid;
    DataSet *ds;
    cls = (*env)->GetObjectClass(env, this);
    fid = (*env)->GetFieldID(env, cls, "dataSet", "J");
    ds = (DataSet *) (*env)->GetObjectField(env, this, fid);
    /* With a DataSet pointer we can use the original API */
    DSshutDown(ds);
}
这是假定了现有服务器通过其API定义的DataSet类型得到 了引用。指向服务器DataSet的指针存储在dataSet域中。当 客户机调用shutDown时,服务器的shutDown方法就会被调用 。因为在服务器实现中声明了要用本机方法来实现shutDown 方法,所以,RMI将直接调用这个本机方法。这个本机方 法找到对象的dataSet域,得到其值,并用它调用现有API 的函数DSshutDown。

Sun公司目前正与ILOG公司合作,开发一种称作TwinPeaks 的产品。TwinPeaks将能够兼容目前的C和C++ API,并生成 Java类,该Java类包含了到Java类中API的调用。这样,您 就能从Java调用现有的任何API。TwinPeaks面市后,将有可 能完全使用Java (而非JNI调用)编写诸如shutDown这样的方 法。

体系结构

RMI系统可为分布式面向对象计算提供简单而又直接的 基础。其体系结构可允许对服务器和引用类型进行扩展 ,从而使RMI能以连续的方式添加功能。

当服务程序被输出后,其引用类型就被定义。在上面 的例子中,我们将服务器作为UnicastRemoteObject服务器输 出,即点到点非复制服务器。对这些对象的引用对于这 类服务器非常合适。不同类型的服务器有不同的引用句 法。例如,MulticastRemoteObject就有允许复制服务的引用句 法。

Figure 3. Stubs and Skeletons


当客户机收到向服务器的引用后,RMI就会下载一个可 将该引用上的调用转换为面向服务器的远程调用的存根 。如图3所示,存根使用对象序列化法将参数编组到方 法中,并通过网络将经过编组的调用送到服务器。在服 务器端,RMI系统接收调用,并连接到一个框架上,而框 架则负责解除参数编组并在服务器上调用该方法的实现 。当服务器的执行完成后,无论返回一个值或抛出一个 例外,框架通过对结果进行编组,并向客户机的存根发 送一个应答。存根解除应答编组,并根据需要返回一个 值或抛出一个例外。存根和框架是用服务器的实现生成 的,通常使用的是程序rmic。存根使用引用与框架进行 会话。这种体系结构使引用能够定义通信的属性。用于 UnicastRemoteObject服务器的引用与在特定主机和端口上运 行的单个服务器对象进行通信。凭借存根/引用的分离 功能,RMI就能添加新的引用类型。处理复制服务器的引 用可将服务器请求多路发送给一组正确的复制程序,汇 集响应,再根据多种响应返回正确的结果。如果服务程 序没有在虚拟机上运行,则另一个引用类型可激活该程 序。客户机可透明地与所有这些引用类型共同工作。

保密与安全

在执行RMI请求时,安全是绝对有保证的。RMI可在客 户机与服务器之间提供安全信道,并可将下载的执行程 序放入安全的“沙箱(sandbox)”中运行,从而保护您的系 统免遭不明客户机可能的攻击。

首先,必须定义您的安全需求,这非常重要。如果您 正在安全的企业网内部执行诸如ComputeServer这样的程序 ,则您只需知道谁在使用计算环路即可,这样您就能对 滥用系统的人进行跟踪。如果您需要提供商业计算服务 器,则您就需要防止多种恶意的破坏。这些都会影响接 口的设计--在企业内部,您可能只要求每个Task对象都 配备人名和部门编号,以便跟踪。而在商业领域中,您 可能需要更高的安全性,包括数字签名身份识别和某些 能帮助您剔除会耗费超过分配于其时间的恶意任务的合 同语言。

在客户机和服务器之间您可能需要有一个安全信道。 RMI可使您能提供可创建包括加密插口(socket)在内的您所 需要的任何类型插口的插口工厂。从JDK 1.2开始,您将 能够指定服务器插口所提供的服务的要求(通过给出对 这些要求的描述)。这种新技术可在小应用程序上采用 ,而多数浏览器都拒绝承诺设置插口工厂。插口要求可 包括加密和其它要求。

下载的类也存在安全问题。Java通过SecurityManager对象 处理安全问题,而该对象可传递所有与安全有关操作的 判断,如打开文件和网络连接等。RMI通过要求您在输出 任何服务对象或在服务器上调用任何方法之前安装安全 管理器,以使用这个标准的Java机制。RMI提供与小应用 程序(无文件存取,只是连接到发出主机等)限制相同的 RMISecurityManager类型。这样可防止下载的执行程序从计算 机上读取或写入数据、或与防火墙后面的其它系统连接 。您也可编写和安装自己的安全管理程序对象,以执行 不同的安全限制。

防火墙

RMI为防火墙后面的客户机提供了与远程服务器进行通 信的方法。这样可使您使用RMI在因特网上部署客户程序 ,如在用于万维网上的小应用程序中。穿越客户机的防 火墙会使通信速度降低,所以RMI成功地采用速度最快的 技术连接客户机与服务器。当客户机首次依次尝试下列 三种可能的方法,试图与服务器建立通信时,该技术即 被UnicastRemoteObject的引用所发现:

  • 用插口(socket)直接与服务器的端口通信。
  • 如果失败,则建立连接到服务器主机和端口上的URL,并 在该URL上使用HTTP POST请求,将信息作为POST的主体发送 给框架。如果成功,则post的结果就是框架对存根的响 应。
  • 如果又失败,则用端口80 (标准HTTP端口)建立连接到服 务器主机的URL,并使用能够将稲MI请求发送给服务器的 CGI程序。
这三种技术哪一种首先成功,将来就会首先被用来与服 务器进行通信。如果这些技术都不成功,则远程方法调 用失败。

这种三步策略使客户机能够以尽可能高的效率实现通 信,并大多使用直接插口连接。在没有安装防火墙的系 统上,或在企业内部防火墙后的通信中,客户机将使用 插口直接连接到服务器上。二级通信技术的速度比直接 通信明显地要慢得多,但允许您编写能在因特网和万维 网上广泛使用的客户程序。

RMI应用在演变的企业中

您现在就可使用RMI连接新的Java应用程序(或小应用 程序)和现有的服务器。在这种情况下,您的企业可随 着Java用途的不断扩展而不断获益。如果您的系统的一部 分是用Java重新编写的,则RMI可使Java的优势从现有Java 组件转移到新的Java程序中。请考虑下面二层次系统中 单一请求在客户机和服务器之间往复转移的路径:


使用RMI意味着您可利用RMI作为客户机与服务器之间 的传输方法,从而使Java的好处遍布整个系统,即使服 务器端在一段时间内仍采用非Java程序。如果您选择用 Java重新编写您的服务器的部分或全部,则您还可从现 有的Java组件中获益。您所能拥有的最重要的Java优点举 例如下:
  • 面向对象的程序重用。能将对象从客户机传递到服务器 和从服务器传递到客户机意味着,您可使用设计模式和 其它面向对象的编程技术提高企业内部程序的重用水平 。
  • 传递属性。在客户机和服务器之间传递的对象可以是另 一端以前从未见过的类型。其实现将被下载以执行新的 属性。
  • 类型安全。Java对象永远是类型安全的,可预防当程序 员对对象的类型产生错误时发生故障。
  • 保密性。Java Classes (类)可以以保密的方式运行,使您 能够与在无保密措施的环境中运行的客户机进行交互工 作。下图表示用Java编写的客户机使用RMI与服务器进行 对话。带阴影的“请求”箭头表示您能获得的Java安全 性、面向对象功能和其它优势:

少量Java代码连接到使用现有服务器API的“传统包裹 程序”。传统包裹程序是Java与现有API之间的桥梁,如 上述实现getUnpaid和shutDown所示。在本图中,我们显示它 是用JNI编写的,但如上所述,传统包裹程序也可以使用 JDBC或TwinPeaks (如果面市的话)编写。

与上图相反,下图表示语言中性系统采用接口定义语 言(通常称作IDL )在客户机和服务器之间实现连接:


仍然必须编写包裹程序,以将用IDL定义的调用连接到 现有服务器API。但如果采用基于IDL的方式,则Java的优 势就会被隔离在客户机一端--因为客户机端的Java在穿 越到服务器之前已经被减小到最小部分。假定您认为用 Java重新编写一些服务程序对您有用。当然这可能有种 种原因,如希望获得安全的Java系统更高的可靠性,或 者因为您希望降低移动成本。或者可能因为向您提供了一 部分服务器实现(Implementation)的供应商推出了能利用Java 优势的更新版本。基于RMI的示意图如下:


多数请求现在都可从Java获得好处。您通过网络在客 户机和服务器之间传递的对象现在对于整个系统都有益 处。例如,您可开始通过您已经定义的相同的远程接口 传递属性,从而提高客户机和服务器的价值。请与基于 IDL的方式做一比较:


您可以获得本来仅限于服务器才有的Java优势,但您 无法利用Java连接客户机/服务器扩展的价值。Java的价 值在IDL边界处被隔断,因为IDL不能假定Java是在连接的 另一侧。如果不抛弃IDL程序,并用RMI重新编写程序, 您就无法充分利用您的系统中Java的优势。

当您在企业的多数环境中使用Java时,这种损失就更 加巨大。使用RMI,您的整个系统中就能获得Java的好处 :


采用基于IDL的方式,用Java重新编写服务器仍然仅让 您享受仅限于服务器的局部的好处:


今天,您可使用RMI连接到服务器,而不必用Java重新 编写服务程序。RMI的使用方法简单,用它创建服务器端 的RMI类非常容易。在这两种情况下,传统包裹程序的复 杂性基本相同。但如果您使用基于IDL的分布式系统,则 您创建的Java程序就不能互相共享Java的好处。RMI现在 可使您轻松地实现连接,而且当您决定扩大Java的使用 范围时,则由于Java在网络两侧具有协同作用,所以您 将获得更多的好处。

结论

RMI为真正面向对象的分布式计算提供了可靠的平台。 您可使用RMI连接到Java组件,或用其它语言编写的现有 的组件。随着Java在您的环境中所具备的重要性的日益 增加,您还可扩大Java的使用范围,并获得所有的好处 --无需移植、低维护成本和安全而保密的环境。RMI为您 提供了循序渐进地将Java扩展到您的系统所有部分的平 台,您可根据需要适时地添加Java服务器和客户机。只 要您添加了Java,那么它所有的好处都会随之而来。RMI 则使之更简单、保密和强大。