RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。(抄的定义)
我们知道远程过程调用(Remote Procedure Call, RPC)可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式对象间的通讯。
1.Stub和Skeleton
RMI框架采用代理来负责客户与远程对象之间通过Socket进行通信的细节。RMI框架为远程对象分别生成了客户端代理和服务器端代理。位于客户端的代理类称为存根(Stub),位于服务器端的代理类称为骨架(Skeleton)。
2.工作原理
3.实现思路
(1)创建远程接口(RemoteInterface):直接或间接继承java.rmi.Remote接口。接口中需要被远程调用的方法,必须抛出RemoteException异常。
(2)创建远程类(RemoteImpl):实现远程接口。为了使远程类的实例变成能为远程客户提供服务的远程对象,可通过以下两种途径之一把它导出(export)为远程对象:
(a). 使远程类实现远程接口时,同时继承java.rmi.server.UnicastRemoteObject类,这是最常用的方式.
public class RemoteImpl extends UnicastRemoteObject implements RemoteInterface{
}
(b)可以在构造方法中调用UnicastRemoteObject类的静态exportObject()方法,同样,远程类的构造方法也必须声明抛出RemoteException。。
public RemoteImpl ()throws RemoteException{
UnicastRemoteObject.exportObject(this,0);
}
(3)创建服务器程序(Server):创建远程对象,通过createRegistry()方法注册远程对象。并通过bind或者rebind方法,把远程对象绑定到指定名称空间(URL)中。
RemoteInterface remoteService = new RemoteImpl();
// 把远程对象注册到RMI注册服务器上,并命名为RemoteObj(名字可自定义,客户端要对应)
// 绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
Naming.rebind(URL, remoteService);// 将stub代理绑定到Registry服务的URL上
(4)创建客户程序(Client):通过 lookup()方法查找远程对象,进行远程方法调用
// // 在RMI服务注册表中查找名称为RemoteObj的对象,并调用其上的方法
// // 客户端通过命名服务Naming获得指向远程对象的远程引用
// RemoteInterface rmObj = (RemoteInterface) Naming.lookup(URL);
// rmObj .doSomething();
4.扩展
(1)RMI技术比较socket的网络编程优缺点对比:
第一、RMI是面向对象的,而后者不是。
第二、RMI是与语言相绑定的。
比如当你使用Java RMI技术的时候,客户端与服务器端都必须使用Java开发。而socket的网络编程是使用独立于开发语言的,甚至独立于平台。基于socket的网络编程,客户端与服务器端可以使用不同开发语言和不同的平台。
第三、从网络协议栈的观点来看,RMI与socket的网络编程处于不同层次上。
基于socket的网络编程位于TCP协议之上,而RMI在TCP协议之上,又定义了自己的应用协议,其传输层采用的是Java远程方法协议(JRMP)。可见,在网络协议栈上,基于RMI的应用位置更高一些,这也决定了,与socket的网络编程相比,RMI会丧失一些灵活性和可控性,但是好处是它带给了应用开发者更多的简洁,方便和易用。比如:如果你用的是RMI,你不需要关心消息是怎么序列化的,你只需要像本地方法调用一样,使用RMI。代价是:应用开发者无法很好地控制消息的序列化机制。
第四、性能:RMI与TCP based socket相比,传输相同的有效数据,RMI需要占用更多的网络带宽。
RMI主要是用于远程方法的”调用“(RMI是多么的名符其实:)),其技术内涵强调的是 “调用”,当你的应用不需要在client与server之间传输大量的数据时,RMI是较好的选择。但是,一旦你的应用需要在client与server之间传输大量的数据,极端的,比如FTP应用,则RMI是不适合的,我们应该使用 socket。
PS: RMI的效率还是很高的,一般情况下会比Hessian更高效,比Web Service更是高效很多;当然和socket相比,当然要低效一点了,socket更底层一些啊。RMI的具体实现,依然是依赖于底层的Socket编程。
(2)RMI使用:
由于RMI调用要求一个远程服务(service)对应一个port,所以管理时要注意一一对应,最好建表。使用时一般结合动态代理使用。
client = (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {classObj}, (obj, mtd, args) -> {
try{
IRMIClient rmiClient = rmiClientMap.get(key);
if (rmiClient == null) {
Registry reg = LocateRegistry.getRegistry(ip, port);
rmiClient = (IRMIClient) reg.lookup(key);
IRMIClient previous = rmiClientMap.putIfAbsent(key, rmiClient);
if (previous != null) {
rmiClient = previous;
}
}
return rmiClient.exec(classObj, new MethodSign(mtd), args);