在分布式系统中,远程方法调用(Remote Method Invocation,简称RMI)是一种在一个Java虚拟机(JVM)中执行的远程对象调用(Remote Object Invocation),它包含了传输对象的远程引用和方法参数,可以让开发者通过在一个Java应用程序中调用远程对象的方法去与其他Java应用程序进行通信,实现分布式计算和通信的功能。
在本文中,我们将介绍如何使用RMI实现Java远程方法调用,包含RMI的工作原理,RMI的架构和如何编写一个RMI应用程序。
一、RMI的工作原理
在RMI中,一个Java对象可以为远程对象(Remote Object) ,”远程对象“是本地JVM上的一份Proxy,当此代理调用方法时,此方法将通过网络传输到远程JVM上去执行,而返回的对象也将被序列化并传回到本地JVM的代理中,主要流程包括三个部分:
1.客户端调用远程方法
RMI客户端通过调用远程对象的方法来和远程服务进行通信,例如客户端调用类似于”remoteObject.calculateArea(2.0,3.0);“的方法来计算长方形的面积。这将导致以下过程发生:
- 设置客户端环境:客户端将本地JVM配置为远程对象的客户端,使用远程对象的接口作为客户端的标记来标识客户端环境
- 获取远程对象:客户端通过名称服务(Registry)寻找远程对象,并获取其远程引用。
- 使用对象:客户端通过远程引用调用远程对象的方法来处理请求。
2.网络传输
当客户端调用远程对象的方法时,远程对象将请求信息打包并通过网络传输到远程JVM。这个过程包括:
- 将方法名、参数和标记打包成对象
- 通过Java RMI工具注册表包装对象,并发布到网络上。
- 远程端的JVM检查注册表并找到远程对象接口的Java类和方法,并从注册表中获取远程引用
- JVM反序列化接收到的请求信息,并通过反射调用方法处理请求。
3.服务端处理请求
远程JVM接收到请求信息后,调度相应的对象处理请求。服务端处理请求的过程包括:
- JVM标记Java对象,在特定端口上监听对远程请求的调用。
- 当调用发生时,将请求传递给对象。
- 执行方法并将结果返回到客户端。
- 序列化返回结果,并通过网络传送到客户端。
二、RMI的架构
RMI具有简单的客户端/服务端体系结构。在这种体系结构中,客户端在本地JVM中使用远程引用调用方法,远程引用则可以在远程JVM上执行远程对象的请求。 RMI API由java.rmi包和java.rmi.server包组成:
- java.rmi:RMI API中的核心部分。该包提供了实现远程接口和远程方法调用所需的类和接口。
- java.rmi.server:RMI服务端的核心部分,该包提供实现远程对象及多个支持类和接口的类。
在启动一个RMI服务时,需要收集如下的组件:远程接口,远程接口实现类,邻近接口实现类,类加载器和远程JVM的名称服务。当客户端启动时,它将搜索命名服务以获取远程对象的引用,然后通过引用调用远程方法。
三、编写一个RMI程序
在理解了RMI架构之后,我们可以开始编写RMI程序。此处我们用一个简单的计算器程序作为示例:
1. 编写服务器端远程接口和实现类
创建Calculating服务器为各种计算提供服务。
首先是远程接口CalculatorService,如下:
```
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface CalculatorService extends Remote {
public double add(double n1, double n2) throws RemoteException;
public double subtract(double n1, double n2) throws RemoteException;
public double multiply(double n1, double n2) throws RemoteException;
public double divide(double n1, double n2) throws RemoteException;
}
```
这是一个标准的Java接口,它声明了四个方法,以执行加,减,乘和除法。
接下来是Implementing Class - CalculatorServiceImpl,它实现了远程接口方法。
```
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class CalculatorServiceImpl extends UnicastRemoteObject implements CalculatorService {
public CalculatorServiceImpl() throws RemoteException {
}
public double add(double n1, double n2) throws RemoteException {
return n1 + n2;
}
public double subtract(double n1, double n2) throws RemoteException {
return n1 - n2;
}
public double multiply(double n1, double n2) throws RemoteException {
return n1 * n2;
}
public double divide(double n1, double n2) throws RemoteException {
return n1 / n2;
}
}
```
此实现类需要扩展Java RMI中的UnicastRemoteObject类,以使其成为远程对象。它的方法将执行远程事务。
2. 编写客户端代码
客户端将使用远程引用来调用远程对象的方法,计算器逻辑是在服务器端完成的。下面是一个简单的客户端实现:
```
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class CalculatorClient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("localhost", 1079);
CalculatorService CalculatorService = (CalculatorService) registry.lookup("CalculatorService");
double d1 = 50.0;
double d2 = 25.0;
System.out.println("Addition : " + CalculatorService.add(d1, d2));
System.out.println("Subtraction : " + CalculatorService.subtract(d1, d2));
System.out.println("Multiplication : " + CalculatorService.multiply(d1, d2));
System.out.println("Division : " + CalculatorService.divide(d1, d2));
}
}
```
在主函数中,客户端通过远程引用查询远程对象并打印结果。
3. 运行程序
在执行此程序之前,需要先启动服务器端。可以通过以下步骤来实现:
- 启动服务器端Java虚拟机
- 启动运行rmi注册表
- 将Java RMI服务注册到注册表中
示例程序中需要手动注册,执行以下步骤:
- 将文件放入classpath或者执行时包含其路径($ CLASSPATH)
- 在服务器端的命令行上执行命令:
```
java java.rmi.registry.LocateRegistry createRegistry 1079
```
- 使用以下命令注册服务器端:
```
java -Djava.rmi.server.codebase=file:/path/to/codebase/ -Djava.rmi.server.hostname=myhost object.rmi.RemoteCalcServer
```
在这个例子中,codebase是我们编写的代码所处的路径,myhost是我们注册该对象的服务器的IP地址。
最后,我们可以在客户端的命令行上执行程序。如果所有步骤都顺利完成,则结果将如下所示:
```
Addition : 75.0
Subtraction : 25.0
Multiplication : 1250.0
Division : 2.0
```
本文介绍了RMI的工作原理,架构和编写RMI应用程序。尽管RMI不如RESTful Web Service或其他新技术流行,但是它仍然是Java引擎中分布式计算和分布式服务的不二选择。