一,rmi 是什么
rmi 全称 remote method invocation ,远程方法调用协议,能让一个 jvm 调用另一个 jvm 上的类的方法。
在 rmi 中涉及到三个部分,客户端,服务端和 registry 注册表, 服务端和注册表并不一定在同一个主机上,三个部分都是可以完全分开的单独部分,服务端需要在 registry 上将一个名称和对应的对象进行,客户端就可以通过注册表使用名称拿到对应的远程对象的 stub,也就是远程对象的 代理,就能像调用本地类对象一样去调用远程对象的方法,但客户端并没有创建一个真正的远程类,只是通过注册表拿到了一个服务端创建的一个 stub 对象,当调用某个方法时就会将名称(注册表中与远程对象绑定的名称)和参数传到服务端,然后服务端的对象调用对应得方法和参数执行操作,再返回结果,真正的执行过程在服务端上执行,客户端只发起请求,传递参数,通过 stub 转发给服务端调用相应的方法执行操作,最后服务端执行并返回结果,同时注册表只在最开始的 lookup 时起桥接作用,充当两个的中间人,当客户端拿到远程对象的 stub 后之后的所有操作都是客户端直接连接服务端操作
二,最小 demo
服务端与注册表:
public static void main(String[] args) throw Exception{ //在本地1099端口创建注册表 LocateRegistry.createRegistry(1099); //创建远程对象 RMIinter rmi = new RMIobj(); //绑定对象到注册表 Nming.bind("rmi://127.0.0.1/1099/hello",rmi);}//远程对象需要继承特定的类和实现特定的接口public interface RMIinter extends Remote { public String hello() throws RemoteException;}public class RMIobj extends UnicastRemoteObject implements RMIinter{ protected RMIobj() throws RemoteException{ super(); } public String hello() throws RemoteException{ System.out.println("hello()被调用"); return("hello"); }}客户端:
public class Client { public static void main(String[] args) throws Exception{ //客户端获取远程的注册表 Registry registry = LocateRegistry.getRegistry("127.0.0.1","1099"); System.out.println(Arrays.toString(registry.list())); //通过注册表查询Hello对应的远程对象· RMIinter stub = (RMIinter)registry.lookup("Hello"); System.out.println(stub.hello()); }}调用过程示例:
客户端调用远程连接 —>客户端 stub 接管调用 —> 参数序列化—> 通过 JRMP 协议发送到服务端 —> 服务端反序列化参数 —> 服务端反序列化参数 —>服务端调用真是远程对象方法 —>返回值被序列化
三,关键类及机制
1.Remote
java.rmi.Remote 是所有远程接口的父接口

该接口不需要实现任何方法,仅作表示当前对象可以远程调用的标识,同时继承该接口的接口需要抛出一个 RemoteException 异常
2.UnicastRemoteObject
rmi 中的关键类,负责将普通的 java 对象导出为一个可以接收远程调用的服务端对象,在服务端创建远程对象的代码中远程对象继承了 UnicastRemoteObject,在继承 UnicastRemoteObject 时对象就已经被导出

构造方法

继续跟进 export 方法

最后继续跟进 export 方法来到了 UnicasrServerRef 类的 exportObject 方法,该方法返回了一个 Remote 对象,也就是将普通的 java 对象变成了一个可以远程调用的 stub,其中涉及到了底层的网络通信,UnicastRemoteObject 类相当于一个上层的接口,隐藏了底层的网络实现细节
3.Registry
rmi 注册中心,是一个接口,保存名字->远程对象
4.RegistryImpl
registry 接口的具体实现类
5.LocateRegistry
创建或获取 registry 的类

getRegistry 获取一个注册表对象的引用,方法只返回了一个 Registry 对象,并没有创建连接,真正的连接在调用 lookup 时
6. RemoteRef / UnicastRef
摘编自 chatgpt:
Stub 内部并不直接处理底层网络通信。真正负责远程调用的是远程引用对象。常见类包括:
可以粗略理解为:
一次远程调用大致是:
四,详细调用链
1.启动注册表

跟进 createRegistry 方法

发现调用了 RegistruImpl,这是真正的注册表对象

涉及到一些底层的网络配置,并且可以看到 RegistryImpl 本身也是一个远程对象,setup 方法调用 exportObject 方法将自己导出

跟进名称与对象的绑定

Naming 对象的 bind 方法实际就是调用的 registry 对象的 bind 方法,只是进行了封装简化了操作,方法中先解析出 url,在拿到 url 对应的注册表,最后调用注册表将传入的名称与对象进行绑定


跟进到注册表的 bind 方法,方法会先检查是否已经存在相同的名称与对象的映射,没有就将这对映射 put 进 bindings,bindings 就是一个 hashtable
checkAccess 是权限检查,注册表通常只允许来自本地的 bind 请求
2.客户端



getRegistry 方法先是做了简单的一些检查,然后创建了一个 LiveRef 对象,该对象保存了对象标识符 ObiID 和对象的位置 TCPEndpoint,相当于贴上了一张名片,表明是一个远程对象,位置在哪,用什么方式连接
然后又 new 了一个 UnicastRef 对象,传入 liveRef,liveRef 是用来标识远程对象,保存相应信息, UnicastRef 类就是用来具体处理该怎么进行远程对象间的连接问题

UnicastRef.invoke(),写了一大堆也看不懂,大概就是创建了网络连接,序列化了方法参数并传输给远程对象