Java RMI(Java Remote Method Invocation)、JRMP、RMI-IIOP

0x01 什么是Java RMI?

Java RMI(Java Remote Method Invocation),即Java远程方法调用。是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。能直接传输序列化后的Java对象和分布式垃圾收集。它的实现依赖于Java虚拟机(JVM),因此它仅支持从一个JVM到另一个JVM的调用

看到这里你知道什么是Java RMI了吗?我比较笨,没看懂。

没关系,我又找了一篇博文,来看下它给出的解释:RMI,是Remote Method Invocation(远程方法调用)的缩写,即在一个JVM中java程序调用在另一个远程JVM中运行的java程序,这个远程JVM既可以在同一台实体机上,也可以在不同的实体机上,两者之间通过网络进行通信。

到这里,好像能看懂点了,RMI顾名思义-远程调用,远程指什么?调用什么?远程指的是同一台主机上不同JVM之间的远程或者两台主机上的JVM的远程,调用的是JVM上运行的程序。

0x02 通过一个例子来理解下

RMI包括以下三个部分:

  • Registry: 提供服务注册与服务获取。即Server端向Registry注册服务,比如地址、端口等一些信息,Client端从Registry获取远程对象的一些信息,如地址、端口等,然后进行远程调用。
  • Server: 远程方法的提供者,并向Registry注册自身提供的服务
  • Client: 远程方法的消费者,从Registry获取远程方法的相关信息并且调用

以下例子根据参考链接改编,一个远程接口IGoods,一个实现接口的类Goods,一个注册远程对象的类PutOnSaleServer,一个客户端调用的类ShoppingClient:

IGoods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.rmi.Remote;
import java.rmi.RemoteException;

/**
* 必须继承Remote接口。
* 所有参数和返回类型必须序列化(因为要网络传输)。
* 任意远程对象都必须实现此接口。
* 只有远程接口中指定的方法可以被调用。
*/
public interface IGoods extends Remote{
// 所有方法必须抛出RemoteException
public double add(double a, double b) throws RemoteException;
public double subtract(double a, double b) throws RemoteException;
public String shopping(String person) throws RemoteException;
}

Goods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/**
* 服务器端实现远程接口。
* 必须继承UnicastRemoteObject,以允许JVM创建远程的存根/代理。
*/
public class Goods extends UnicastRemoteObject implements IGoods {

private int numberOfComputations;

protected Goods() throws RemoteException {
numberOfComputations = 0;
}

@Override
public double add(double a, double b) throws RemoteException {
numberOfComputations++;
System.out.println("Number of computations performed so far = "
+ numberOfComputations);
return (a+b);
}

@Override
public double subtract(double a, double b) throws RemoteException {
numberOfComputations++;
System.out.println("Number of computations performed so far = "
+ numberOfComputations);
return (a-b);
}

@Override
public String shopping(String person) throws RemoteException {
System.out.println("Number of computations performed so far = "
+ numberOfComputations);
System.out.println(person + "is Shopping");
String a = person + " is shopping!";
return a;
}
}

PutOnSaleServer.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/**
* 创建IGoods类的实例并在rmiregistry中注册。
*/
public class PutOnSaleServer {

public static void main(String[] args) {

try {
// 注册远程对象,向客户端提供远程对象服务。
// 远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称,
// 但是,将远程对象注册到RMI Registry之后,
// 客户端就可以通过RMI Registry请求到该远程服务对象的stub,
// 利用stub代理就可以访问远程服务对象了。
IGoods goods = new Goods();
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
registry.bind("Compute", goods);
System.out.println("货物上架完成");
} catch (Exception e) {
e.printStackTrace();
}

}
}

ShoppingClient.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class ShoppingClient {

public static void main(String[] args) {

try {
// 如果RMI Registry就在本地机器上,URL就是:rmi://localhost:1099/hello
// 否则,URL就是:rmi://RMIService_IP:1099/hello
Registry registry = LocateRegistry.getRegistry("localhost");
// 从Registry中检索远程对象的存根/代理
IGoods goods = (IGoods) registry.lookup("Compute");
// 调用远程对象的方法
System.out.println(goods.shopping("小陈"));
} catch (Exception e) {
e.printStackTrace();
}

}
}

运行结果:

image-20200609170235235 image-20200609170331156

现在看完例子,可以梳理下:

image-20200609170552480

货物-远程对象、备货员-server、货架-骨架;

消费者-client、购买凭证-存根;

小货仓-RMI Registry

可以把Java RMI理解成地铁站的无人售货机

1、备货员先将货物信息存到小货仓,保证售货机有货物

2、消费者购买需要在显示屏的找到要买的物品然后进行支付,支付完成获取到了购买凭证

3、消费者通过购买凭证,告诉售货机我要获取某个货物,然后售货机在货架上找到目标货物推到售货口,消费者通过可以获取到货物。

0x03 扩展

RMI原理

  1. 将可以远程调用的对象进行序列化,然后绑定到RMI Server(被调方,运行者)中作为存根(stub)
  2. RMI Client 会先去下载stub反序列化然后发起client调用,RMI 底层(RMI Interface Layer & Transport Layer)会讲请求参数封装发送到RMI Server
  3. RMI Server 接收到封装的参数,传递给骨架(skeleton),由桩解析参数并且以参数调用对应的存根(stub)方法。
  4. 存根方法在RMI Server执行完毕之后,返回结果将被RMI底层封装并传输给RMI Client(也就是主调方,调用者)

这里有一点需要注意:RMI是基于序列化和反序列化的,远程对象在进行注册时,序列化作为存根,客户端调用的时候会把序列化后的对象进行反序列化再调用。

参考链接:

https://www.jianshu.com/p/de85fad05dcb

https://www.jianshu.com/p/5c6f2b6d458a

https://segmentfault.com/a/1190000016598069

https://blog.csdn.net/qq_28081453/article/details/83279066

https://www.cnblogs.com/lvyahui/p/5425507.html