# 15.2.1　导出 RMI 服务

如果你曾经创建过 RMI 服务，应该会知道这会涉及如下几个步骤：

1. 编写一个服务实现类，类中的方法必须抛出 java.rmi.RemoteException 异常；
2. 创建一个继承于 java.rmi.Remote 的服务接口；
3. 运行 RMI 编译器（rmic），创建客户端 stub 类和服务端 skeleton 类；
4. 启动一个 RMI 注册表，以便持有这些服务；
5. 在 RMI 注册表中注册服务。

哇！发布一个简单的 RMI 服务需要做这么多的工作。除了这些必需的步骤外，你可能注意到了，会抛出相当多的 RemoteException 和 MalformedURLException 异常。虽然这些异常通常意味着一个无法从 catch 代码块中恢复的致命错误，但是我们仍然需要编写样板式的代码来捕获并处理这些异常 —— 即使我们不能修复它们。

很明显，发布一个 RMI 服务涉及到大量的代码和手工作业。Spring 是否能够做一些工作来让这些事情变得不再那么棘手呢？

**在 Spring 中配置 RMI 服务**

幸运的是，Spring 提供了更简单的方式来发布 RMI 服务，不用再编写那些需要抛出 RemoteException 异常的特定 RMI 类，只需简单地编写实现服务功能的 POJO 就可以了，Spring 会处理剩余的其他事项。

我们将要创建的 RMI 服务需要发布 SpitterService 接口中的方法，如下的程序清单展现了该接口定义。

{% code title="程序清单 15.1 SpitterService 定义了 Spittr 应用的服务层" %}

```java
package com.habuma.spittr.service;

import java.util.List;
import com.habuma.spittr.domain.Spitter;
import com.habuma.spittr.domain.Spittle;

public interface SpitterService {
  List<Spittle> getRecentSpittles(int count);
  void saveSpittle{Spittle spittle);
  void saveSpitter{Spitter spitter);
  Spitter getSpitter(long id);
  void startFollowing(Spitter follower, Spitter followee);
  List<Spittle> getSpittlesForSpitter(Spitter spitter);
  List<Spittle> getSpittlesForSpitter(String username);
  Spitter getSpitter(String username);
  Spittle getSpittleById(long id);
  void deleteSpittle(long id);
  List<Spitter> getAllSpitters();
}
```

{% endcode %}

如果我们使用传统的 RMI 来发布此服务，SpitterService 和 SpitterServiceImpl 中的所有方法都需要抛出 java.rmi.RemoteException。但是如果我们使用 Spring 的 RmiServiceExporter 把该类转变为 RMI 服务，那现有的实现不需要做任何改变。

RmiServiceExporter 可以把任意 Spring 管理的 bean 发布为 RMI 服务。如图 15.4 所示，RmiServiceExporter 把 bean 包装在一个适配器类中，然后适配器类被绑定到 RMI 注册表中，并且代理到服务类的请求 —— 在本例中服务类也就是 SpitterServiceImpl。

![图 15.4　RmiServiceExporter 把 POJO 包装到服务适配器中，并将服务适配器绑定到 RMI 注册表中，从而将 POJO 转换为 RMI 服务](https://3784153818-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmcjU5gG__lBrRbUxBO%2F-LnlKqpvjUPgTVy2hdxF%2F-LnlMfXclFRR5XzERyXj%2F15.4%20%E8%BD%AC%E6%8D%A2%E4%B8%BA%20RMI%20%E6%9C%8D%E5%8A%A1.jpg?alt=media\&token=c7a8f685-0216-4cb2-b162-e82cd5e802c7)

使用 RmiServiceExporter 将 SpitterServiceImpl 发布为 RMI 服务的最简单方式是在 Spring 中使用如下的 @Bean 方法进行配置：

```java
@Bean
public RmiServiceExporter rmiExporter(SpitterService spitterService) {
  RmiServiceExporter rmiExporter = new RmiServiceExporter();
  rmiExporter.setService(spitterService);
  rmiExporter.setServiceName("SpitterService");
  rmiExporter.setServiceInterface(SpitterService.class);
  return rmiExporter;
}
```

这里会把 spitterServicebean 设置到 service 属性中，表明 RmiServiceExporter 要把该 bean 发布为一个 RMI 服 务。serviceName 属性命名了 RMI 服务，serviceInterface 属性指定了此服务所实现的接口。

默认情况下，RmiServiceExporter 会尝试绑定到本地机器 1099 端口上的 RMI 注册表。如果在这个端口没有发现 RMI 注册表，RmiServiceExporter 将会启动一个注册表。如果希望绑定到不同端口或主机上的 RMI 注册表，那么我们可以通过 registryPort 和 registryHost 属性来指定。例如，下面的 RmiServiceExporter 会尝试绑定 rmi.spitter.com 主机 1199 端口上的 RMI 注册表：

```java
@Bean
public RmiServiceExporter rmiExporter(SpitterService spitterService) {
  RmiServiceExporter rmiExporter = new RmiServiceExporter();
  rmiExporter.setService(spitterService);
  rmiExporter.setServiceName("SpitterService");
  rmiExporter.setServiceInterface(SpitterService.class);
  rmiExporter.setRegistryHost("rmi.spitter.com");
  rmiExporter.setRegistryPort(1199);
  return rmiExporter;
}
```

这就是我们使用 Spring 把某个 bean 转变为 RMI 服务所需要做的全部工作。现在 Spitter 服务已经导出为 RMI 服务，我们可以为 Spittr 应用创建其他的用户界面或邀请第三方使用此 RMI 服务创建新的客户端。如果使用 Spring，客户端开发者访问 Spitter 的 RMI 服务会非常容易。

让我们转换一下视角来看看如何编写 Spitter RMI 服务的客户端。
