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 接口中的方法,如下的程序清单展现了该接口定义。

程序清单 15.1 SpitterService 定义了 Spittr 应用的服务层
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();
}

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

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

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

@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 注册表:

@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 服务的客户端。

Last updated