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 的防火墙配置和序列化安全性问题。