文档

Java™教程
隐藏目录
实现远程接口
路径: RMI
章节: 编写RMI服务器

实现远程接口

本节讨论了实现计算引擎类的任务。一般来说,实现远程接口的类至少应该完成以下几个任务:

RMI服务器程序需要创建初始的远程对象并将其导出到RMI运行时,以使其可以接收传入的远程调用。这个设置过程可以封装在远程对象实现类本身的方法中,也可以包含在另一个类中。设置过程应该完成以下几个任务:

计算引擎的完整实现如下所示。 engine.ComputeEngine 类实现了远程接口 Compute,并且还包括了用于设置计算引擎的 main 方法。以下是 ComputeEngine 类的源代码:

package engine;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import compute.Compute;
import compute.Task;

public class ComputeEngine implements Compute {

    public ComputeEngine() {
        super();
    }

    public <T> T executeTask(Task<T> t) {
        return t.execute();
    }

    public static void main(String[] args) {
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new SecurityManager());
        }
        try {
            String name = "Compute";
            Compute engine = new ComputeEngine();
            Compute stub =
                (Compute) UnicastRemoteObject.exportObject(engine, 0);
            Registry registry = LocateRegistry.getRegistry();
            registry.rebind(name, stub);
            System.out.println("ComputeEngine bound");
        } catch (Exception e) {
            System.err.println("ComputeEngine exception:");
            e.printStackTrace();
        }
    }
}

以下各节将讨论计算引擎实现的每个组成部分。

声明正在实现的远程接口

计算引擎的实现类声明如下:

public class ComputeEngine implements Compute

此声明表示该类实现了Compute远程接口,因此可以用作远程对象。

ComputeEngine类定义了一个远程对象实现类,该类实现了一个远程接口和没有其他接口。 ComputeEngine类还包含两个只能在本地调用的可执行程序元素。其中之一是ComputeEngine实例的构造函数。第二个元素是一个main方法,用于创建ComputeEngine实例并使其对客户端可用。

定义远程对象的构造函数

ComputeEngine类有一个不带参数的构造函数。构造函数的代码如下:

public ComputeEngine() {
    super();
}

此构造函数只调用超类构造函数,即Object类的无参数构造函数。虽然即使从ComputeEngine构造函数中省略了超类构造函数,超类构造函数仍会被调用,但出于清晰起见,它被包含在内。

为每个远程方法提供实现

远程对象的类为每个在远程接口中指定的远程方法提供实现。 Compute接口包含一个远程方法executeTask,其实现如下:

public <T> T executeTask(Task<T> t) {
    return t.execute();
}

此方法实现了ComputeEngine远程对象与其客户端之间的协议。每个客户端都向ComputeEngine提供一个具有Task接口的特定实现的Task对象的实例。 ComputeEngine执行每个客户端的任务,并将任务的execute方法的结果直接返回给客户端。

在RMI中传递对象

远程方法的参数或返回值可以是几乎任何类型,包括本地对象、远程对象和原始数据类型。更准确地说,只要实体是基本数据类型、远程对象或可序列化对象(即实现java.io.Serializable接口),就可以将任何类型的实体传递给远程方法或从远程方法返回。

一些对象类型不符合这些标准,因此无法传递给远程方法或从远程方法返回。其中大多数对象,如线程或文件描述符,封装的信息只在单个地址空间内才有意义。许多核心类,包括java.langjava.util包中的类,实现了Serializable接口。

有关参数和返回值传递规则的规定如下:

通过引用传递远程对象意味着通过远程方法调用对对象状态所做的任何更改都会反映在原始远程对象中。当传递远程对象时,只有那些远程接口才对接收方可用。实现类中定义的任何方法或类实现的非远程接口中定义的任何方法都对接收方不可用。

例如,如果您传递对ComputeEngine类实例的引用,接收方只能访问计算引擎的executeTask方法。该接收方将无法看到ComputeEngine构造函数,其main方法,或其对java.lang.Object的任何方法的实现。

在远程方法调用的参数和返回值中,非远程对象是按值传递的。因此,在接收Java虚拟机中创建对象的副本。接收方对对象状态的任何更改仅反映在接收方的副本中,而不反映在发送方的原始实例中。发送方对对象状态的任何更改仅反映在发送方的原始实例中,而不反映在接收方的副本中。

实现服务器的main方法

ComputeEngine实现中最复杂的方法是main方法。main方法用于启动ComputeEngine,因此需要进行必要的初始化和管理工作,以准备服务器接受来自客户端的调用。此方法不是远程方法,这意味着它不能从不同的Java虚拟机中调用。由于main方法声明为static,因此该方法与任何对象都不相关,而是与ComputeEngine类相关。

创建和安装安全管理器

main方法的第一个任务是创建和安装安全管理器,它保护来自Java虚拟机中运行的不受信任的下载代码对系统资源的访问。安全管理器确定下载的代码是否可以访问本地文件系统或执行任何其他特权操作。

如果RMI程序未安装安全管理器,则RMI不会为作为远程方法调用的参数或返回值接收到的对象下载类(除了从本地类路径)。这个限制确保下载的代码执行的操作受到安全策略的约束。

这是创建和安装安全管理器的代码:

if (System.getSecurityManager() == null) {
    System.setSecurityManager(new SecurityManager());
}

使远程对象对客户端可用

接下来,main方法创建了一个ComputeEngine实例,并使用以下语句将其导出到RMI运行时:

Compute engine = new ComputeEngine();
Compute stub =
    (Compute) UnicastRemoteObject.exportObject(engine, 0);

静态的UnicastRemoteObject.exportObject方法导出提供的远程对象,以便它可以从远程客户端接收对其远程方法的调用。第二个参数是一个int,用于指定用于侦听该对象的传入远程调用请求的TCP端口。通常使用值零,表示使用匿名端口。实际端口将由RMI或底层操作系统在运行时选择。但也可以使用非零值指定要用于侦听的特定端口。一旦exportObject调用成功返回,ComputeEngine远程对象就准备好处理传入的远程调用。

exportObject方法返回导出的远程对象的存根。请注意,变量stub的类型必须是Compute,而不是ComputeEngine,因为远程对象的存根只实现了导出的远程对象实现的远程接口。

exportObject方法声明可以抛出RemoteException,这是一种受检异常类型。main方法使用其try/catch块处理此异常。如果不以这种方式处理异常,RemoteException必须在main方法的throws子句中声明。如果必要的通信资源不可用,例如如果请求的端口已绑定到其他用途,则尝试导出远程对象可能会抛出RemoteException

在客户端可以调用远程对象上的方法之前,它必须首先获得对远程对象的引用。获取引用可以通过与程序中获取任何其他对象引用的方式完成,例如作为方法的返回值的一部分或作为包含此类引用的数据结构的一部分。

系统提供了一种特殊类型的远程对象,即RMI注册表,用于查找其他远程对象的引用。RMI注册表是一个简单的远程对象命名服务,可以通过名称获取对远程对象的引用。注册表通常仅用于定位RMI客户端需要使用的第一个远程对象。然后,该第一个远程对象可能提供查找其他对象的支持。

java.rmi.registry.Registry远程接口是用于在注册表中绑定(或注册)和查找远程对象的API。java.rmi.registry.LocateRegistry类提供了用于合成到特定网络地址(主机和端口)的注册表的远程引用的静态方法。这些方法创建包含指定网络地址的远程引用对象,而不执行任何远程通信。LocateRegistry还提供了用于在当前Java虚拟机中创建新注册表的静态方法,尽管此示例不使用这些方法。一旦远程对象在本地主机上的RMI注册表中注册,任何主机上的客户端都可以通过名称查找远程对象,获取其引用,然后调用对象上的远程方法。注册表可以由在主机上运行的所有服务器共享,或者单个服务器进程可以创建和使用自己的注册表。

ComputeEngine类使用以下语句为对象创建一个名称:

String name = "Compute";

然后,代码将名称添加到服务器上运行的RMI注册表中。稍后使用以下语句执行此步骤:

Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);

rebind调用对本地主机上的RMI注册表进行远程调用。与任何远程调用一样,此调用可能导致抛出RemoteException,这由main方法末尾的catch块处理。

请注意以下关于Registry.rebind调用的内容:

一旦服务器在本地RMI注册表中注册,它会打印一条指示准备开始处理调用的消息。然后,main方法完成。不需要让线程等待以保持服务器运行。只要在另一个Java虚拟机中有对ComputeEngine对象的引用,无论是本地还是远程的,ComputeEngine对象都不会被关闭或垃圾回收。因为程序在注册表中绑定了对ComputeEngine的引用,所以它可以被远程客户端(注册表本身)访问。RMI系统保持ComputeEngine的进程运行。只有当从注册表中删除其绑定并且没有远程客户端持有ComputeEngine对象的远程引用时,才会回收ComputeEngine

ComputeEngine.main方法中的最后一段代码处理可能出现的任何异常。在代码中可能抛出的唯一检查异常类型是RemoteException,可以通过UnicastRemoteObject.exportObject调用或注册表rebind调用引发。无论哪种情况,程序在打印错误消息后不能做更多的事情,只能退出。在某些分布式应用程序中,可以从无法进行远程调用的故障中恢复。例如,应用程序可以尝试重新尝试操作或选择另一个服务器继续操作。


上一页:设计远程接口
下一页:创建客户端程序