最近在学习Dubbo,RMI是很重要的底层机制,RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制。使用这种机制,某一台计算机(即JVM虚拟机)上的对象可以调用另外一台计算机上的对象来获取远程数据。
RMI的实现对建立分布式Java应用程序至关重要,是Java体系非常重要的底层技术。
1.RMI的概念和原理
RMI思路是在客户端安装一个代理(proxy),代理是位于客户端虚拟机中的一个对象,对于客户端对象,看起来就像访问的远程对象一样,客户端代理会使用网络协议与服务器进行通信。
同样的服务端也会有一个代理对象来进行通信的繁琐工作。
在RMI中,客户端的代理对象被称为存根(Stub),存根位于客户端机器上,它知道如何通过网络与服务器联系。存根会将远程方法所需的参数打包成一组字节。对参数编码的过程被称为参数编组(parameter marshalling),参数编组的目的是将参数转换成适合在虚拟机之间进行传递的形式。在RMI协议中,对象时使用序列化机制进行编码的。
总的来说,客户端的存根方法构造了一个信息块,它由以下几部分组成
被使用的远程对象的标识符;
被调用的方法的描述;
编组后的参数;
然后,存根将此信息发送给服务器。在服务器的一端,接收器对象执行以下动作:
定位要调用的远程对象;
调用所需的方法,并传递客户端提供的参数;
捕获返回值或调用产生的异常;
将返回值编组,打包送回给客户端存根;
客户端存根对来自服务器端的返回值或异常进行反编组,其结果就成为了调用存根返回值。
2.RMI体系结构
桩/框架(Stub/Skeleton)层:客户端的桩和服务器端的框架;
远程引用(remote reference)层:处理远程引用行为
传送层(transport):连接的建立和管理,以及远程对象的跟踪
3.RMI与代理模式
RMI的实现是典型的代理模式思想。
(1)代理模式的UML图
代理模式为其他对象提供一种代理以控制对这个对象的访问,把调用者与被调用者分离开,由代理负责传递信息来完成调用。
比如你想买美版Iphone6s,朋友出国,帮你海淘带回,整个过程就是代理模式,朋友作为代理,代你完成你想进行的操作。
代理模式能将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度;
在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用,代理对象也可以对目标对象调用之前进行其他操作。
代理模式在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢;同时也增加了系统的复杂度。
(2)代理模式在RMI这种的体现
远程代理的内部机制是这样的:
下面的RMI实例,其实就是一个代理模式的应用。
4.RMI的简单实例
RMI的开发步骤如下:
先创建远程接口及声明远程方法,注意这是实现双方通讯的接口,需要继承Remote
开发一个类来实现远程接口及远程方法,值得注意的是实现类需要继承UnicastRemoteObject
通过javac命令编译文件,通过java -server 命令注册服务,启动远程对象
最后客户端查找远程对象,并调用远程方法
(1)服务端远程接口
创建远程接口SearchService,接口必须继承Remote类,每一个定义方法都要抛出RemoteException异常
import java.rmi.Remote; import java.rmi.RemoteException; public interface SearchService extends Remote { public User findUser(String id) throws RemoteException;; }
(2)建立SearchServiceImpl实现远程接口,注意此为远程对象实现类,需要继承UnicastRemoteObject
import java.rmi.RemoteException; public class SearchServiceImpl implements SearchService{ /** * 抛出RemoteException * @throws RemoteException */ public SearchServiceImpl() throws RemoteException { super(); } @Override public User findUser(String id) throws RemoteException { /** * 模拟查找返回数据 */ User user=new User(id,"Tom","18岁"); return user; } }
(3)为服务建立一个Model层,此对象需要远程传输,所以必须实现implements Serializable序列化接口,也就是可以在client和server端进行传输的可序列化对象
新建User类,用于数据传输:
public class User implements Serializable{ private static final long serialVersionUID = 1L; private String id; private String name; private String age; public User(String id,String name,String age){ this.id=id; this.name=name; this.age=age; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String toString(){ StringBuffer sb=new StringBuffer(); sb.append("~用户id-"+id); sb.append("~用户姓名-"+id); sb.append("~用户年龄-"+id); return sb.toString(); } }
(4)建立服务器端,在服务器端注册RMI通讯端口与通讯路径
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; /** * @author BingYue */ public class Process { public static void main(String[] args){ try { SearchService searchService=new SearchServiceImpl(); //注册通信端口 Registry registry=LocateRegistry.createRegistry(5678); //注册通讯路径 Naming.rebind("rmi://127.0.0.1:5678/searchService", searchService); System.out.println("Service Start!"); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
(5)创建客户端,导入上面的实体类和接口,通过Naming.lookup()的方式调用
如果使用IDE,就可以新建一个项目,Client代码如下:
import java.rmi.Naming; /** * @author BingYue */ public class Client { public static void main(String[] args){ try { //调用远程对象,注意RMI路径与接口必须与服务器配置一致 SearchService searchService=(SearchService)Naming.lookup("rmi://127.0.0.1:5678/searchService"); User user=searchService.findUser("100"); System.out.println(user.toString()); } catch (Exception e) { e.printStackTrace(); } } }
(6)分别运行服务端和客户端,获得远程调用结果
(1)Remote接口:是一个不定义方法的标记接口
Public interface Remote{ }
原文地址:https://www.cnblogs.com/jack-Star/p/8277822.html