# 实现远程接口

> 原文： [https://docs.oracle.com/javase/tutorial/rmi/implementing.html](https://docs.oracle.com/javase/tutorial/rmi/implementing.html)

本节讨论为计算引擎实现类的任务。通常，实现远程接口的类至少应该执行以下操作：

*   声明正在实现的远程接口
*   定义每个远程对象的构造函数
*   为远程接口中的每个远程方法提供实现

RMI 服务器程序需要创建初始远程对象，并 _ 将它们 _ 导出到 RMI 运行时，这使它们可用于接收传入的远程调用。此设置过程可以封装在远程对象实现类本身的方法中，也可以完全包含在另一个类中。设置过程应执行以下操作：

*   创建并安装安全管理器
*   创建和导出一个或多个远程对象
*   使用 RMI 注册表（或使用另一个命名服务，例如可通过 Java 命名和目录接口访问的服务）注册至少一个远程对象以进行引导

随后是计算引擎的完整实现。 [``engine.ComputeEngine`` ](examples/engine/ComputeEngine.java)类实现远程接口`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`接口的`execute`方法的特定实现。 `ComputeEngine`执行每个客户端的任务，并将任务的`execute`方法的结果直接返回给客户端。

## 在 RMI 中传递对象

远程方法的参数或返回值几乎可以是任何类型，包括本地对象，远程对象和原始数据类型。更准确地说，任何类型的任何实体都可以传递给远程方法或从远程方法传递，只要该实体是原始数据类型，远程对象或 _ 可序列化 _ 对象的类型的实例，这意味着它实现了接口`java.io.Serializable.`

某些对象类型不符合任何这些条件，因此无法传递给远程方法或从远程方法返回。大多数这些对象（例如线程或文件描述符）封装仅在单个地址空间内有意义的信息。许多核心类，包括包`java.lang`和`java.util`中的类，都实现了`Serializable`接口。

控制参数和返回值如何传递的规则如下：

*   远程对象基本上是通过引用传递的。 _ 远程对象引用 _ 是一个存根，它是一个客户端代理，它实现远程对象实现的完整远程接口集。
*   使用对象序列化通过副本传递本地对象。默认情况下，除了标记为`static`或`transient`的字段外，将复制所有字段。可以在逐个类的基础上覆盖默认序列化行为。

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

例如，如果要传递对`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);

```

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

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

`exportObject`方法声明它可以抛出`RemoteException`，这是一种经过检查的异常类型。 `main`方法使用`try` / `catch`块处理此异常。如果没有以这种方式处理异常，则必须在`main`方法的`throws`子句中声明`RemoteException`。如果必要的通信资源不可用，则尝试导出远程对象可以抛出`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`调用的信息：

*   `LocateRegistry.getRegistry`的无参数重载合成对本地主机和默认注册表端口 1099 上的注册表的引用。如果注册表是在除以外的端口上创建的，则必须使用具有`int`参数的重载。 1099。
*   在注册表上进行远程调用时，将传递远程对象的存根，而不是远程对象本身的副本。远程实现对象（例如`ComputeEngine`的实例）永远不会离开创建它们的 Java 虚拟机。因此，当客户端在服务器的远程对象注册表中执行查找时，将返回存根的副本。因此，在这种情况下，远程对象通过（远程）引用而不是通过值有效地传递。
*   出于安全原因，应用程序只能在同一主机上运行的注册表中使用`bind`，`unbind`或`rebind`远程对象引用。此限制可防止远程客户端删除或覆盖服务器注册表中的任何条目。但是，可以从任何本地或远程主机请求`lookup`。

一旦服务器向本地 RMI 注册表注册，它就会打印一条消息，指示它已准备好开始处理呼叫。然后，`main`方法完成。没有必要让线程等待以使服务器保持活动状态。只要在另一个 Java 虚拟机（本地或远程）中引用`ComputeEngine`对象，就不会关闭`ComputeEngine`对象或收集垃圾。因为程序绑定了对注册表中`ComputeEngine`的引用，所以可以从远程客户端（注册表本身）访问它。 RMI 系统使`ComputeEngine`的进程保持运行。 `ComputeEngine`可用于接受呼叫，并且在从注册表 _ 和 _ 中删除其绑定之前不会回收[AFG1]没有远程客户端持有对`ComputeEngine`对象的远程引用。

`ComputeEngine.main`方法中的最后一段代码处理可能出现的任何异常。可以在代码中抛出的唯一已检查异常类型是`RemoteException`，可以是`UnicastRemoteObject.exportObject`调用，也可以是注册表`rebind`调用。在任何一种情况下，程序都不能做多，只能在打印错误消息后退出。在某些分布式应用程序中，可以从无法进行远程调用的恢复中恢复。例如，应用程序可能会尝试重试该操作或选择其他服务器来继续操作。