1. 概述

Java 远程方法调用(RMI)允许调用驻留在不同 Java 虚拟机中的对象。虽然这是项成熟的技术,但直接使用相当繁琐,正如 Oracle 官方教程 所展示的那样。

本文将探讨 Spring Remoting 如何更简洁、更优雅地使用 RMI。作为 Spring Remoting 系列的收官之作,本文将补充之前讨论的其他技术:HTTP Invokers、JMS、AMQP、Hessian 和 Burlap。

2. Maven 依赖

我们将创建两个 Spring Boot 应用:服务端(暴露远程服务)和客户端(调用服务)。核心依赖只需 spring-context,这里使用 spring-boot-starter-web 并排除嵌入式 Tomcat:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

⚠️ 排除 Tomcat 是因为我们不需要 Web 容器,RMI 使用自己的通信机制。

3. 服务端实现

3.1 定义服务接口

声明一个出租车预订服务接口,这是客户端调用的契约:

public interface CabBookingService {
    Booking bookRide(String pickUpLocation) throws BookingException;
}

3.2 实现服务逻辑

创建接口实现类,处理实际业务逻辑:

@Bean 
CabBookingService bookingService() {
    return new CabBookingServiceImpl();
}

3.3 暴露 RMI 服务

使用 RmiServiceExporter 将服务暴露给客户端:

@Bean 
RmiServiceExporter exporter(CabBookingService implementation) {
    Class<CabBookingService> serviceInterface = CabBookingService.class;
    RmiServiceExporter exporter = new RmiServiceExporter();
    exporter.setServiceInterface(serviceInterface);
    exporter.setService(implementation);
    exporter.setServiceName(serviceInterface.getSimpleName());
    exporter.setRegistryPort(1099); 
    return exporter;
}

关键配置说明:

  • setServiceInterface():指定远程调用的接口
  • setService():注入实际业务实现
  • setRegistryPort():设置 RMI 注册端口(默认 1099)
  • setServiceName():定义服务在注册中心的唯一标识

客户端可通过 rmi://HOST:1099/CabBookingService 访问服务。Spring 会自动启动 RMI 注册中心,无需手动操作。

4. 客户端调用

4.1 创建服务代理

使用 RmiProxyFactoryBean 创建与服务端接口一致的代理 Bean:

@Bean 
RmiProxyFactoryBean service() {
    RmiProxyFactoryBean rmiProxyFactory = new RmiProxyFactoryBean();
    rmiProxyFactory.setServiceUrl("rmi://localhost:1099/CabBookingService");
    rmiProxyFactory.setServiceInterface(CabBookingService.class);
    return rmiProxyFactory;
}

4.2 调用远程服务

在客户端主方法中获取代理并调用服务:

public static void main(String[] args) throws BookingException {
    CabBookingService service = SpringApplication
      .run(RmiClient.class, args).getBean(CabBookingService.class);
    Booking bookingOutcome = service
      .bookRide("13 Seagate Blvd, Key Largo, FL 33037");
    System.out.println(bookingOutcome);
}

启动客户端即可验证远程调用是否成功。这种实现方式隐藏了 RMI 的底层复杂性,调用本地接口就像调用远程服务一样简单。

5. 总结

Spring Remoting 通过以下方式简化了 RMI 开发:

  • ✅ 自动管理 RMI 注册中心
  • ✅ 消除手动处理检查异常的繁琐
  • ✅ 将远程调用封装为本地接口调用

相比原生 RMI,这种方案更符合现代开发习惯。完整代码示例可在 GitHub 获取。

踩坑提醒:生产环境中需注意 RMI 的防火墙配置和序列化安全性问题。


原始标题:Spring Remoting with RMI