#0 系列目录#java
#1 简介# RMI是远程方法调用的简称,像其名称暗示的那样,它可以帮助咱们查找并执行远程对象的方法。通俗地说,远程调用就象将一个class放在A机器上,而后在B机器中调用这个class的方法
。编程
我我的认为,尽管RMI不是惟一的企业级远程对象访问方案,但它倒是最容易实现的。与可以使不一样编程语言开发的CORBA不一样的是,RMI是一种纯Java解决方案。在RMI中,程序的全部部分都由Java编写
。服务器
#2 概念# 我在前面已经提到,RMI是一种远程方法调用机制,其过程对于最终用户是透明的:在进行现场演示时,若是我不说它使用了RNI,其余人不可能知道调用的方法存储在其余机器上。固然了,二台机器上必须都安装有Java虚拟机(JVM)。网络
其余机器须要调用的对象必须被导出到远程注册服务器,这样才能被其余机器调用。所以,若是机器A要调用机器B上的方法,则机器B必须将该对象导出到其远程注册服务器。注册服务器是服务器上运行的一种服务,它帮助客户端远程地查找和访问服务器上的对象
。一个对象只有导出来后,而后才能实现RMI包中的远程接口。例如,若是想使机器A中的Xyz对象可以被远程调用,它就必须实现远程接口。框架
RMI须要使用占位程序和框架,占位程序在客户端,框架在服务器端
。在调用远程方法时,咱们无需直接面对存储有该方法的机器。异步
在进行数据通信前,还必须作一些准备工做。占位程序就象客户端机器上的一个本机对象,它就象服务器上的对象的代理,向客户端提供可以被服务器调用的方法
。而后,Stub就会向服务器端的Skeleton发送方法调用,Skeleton就会在服务器端执行接收到的方法。编程语言
Stub和Skeleton之间经过远程调用层进行相互通信,远程调用层遵循TCP/IP协议收发数据
。下面咱们来大体了解一种称为为“绑定”的技术。分布式
客户端不管什么时候要调用服务器端的对象,你可曾想过他是如何告诉服务器他想建立什么样的对象吗?这正是“绑定”的的用武之地。在服务器端,咱们将一个字符串变量与一个对象联系在一块儿(能够经过方法来实现),客户端经过将那个字符串传递给服务器来告诉服务器它要建立的对象,这样服务器就能够准确地知道客户端须要使用哪个对象了
。全部这些字符串和对象都存储在的远程注册服务器中。ide
#3 实现# 在研究代码以前,咱们来看看必须编写哪些代码:微服务
**远程对象:**这个接口只定义了一个方法。咱们应当明白的是,这个接口并不是老是不包括方法的代码而只包括方法的定义。远程对象包含要导出的每一个方法的定义,它还实现Java.rmi中的远程接口。
**远程对象实现:**这是一个实现远程对象的类。若是实现了远程对象,就可以覆盖该对象中的全部方法,所以,远程对象的实现类将真正包含咱们但愿导出的方法的代码。
**远程服务器:**这是一个做为服务器使用的类,它是相对于要访问远程方法的客户端而言的。它存储着绑定的字符串和对象。
**远程客户端:**这是一个帮助咱们访问远程方法提供帮助的类,它也是最终用户。咱们将使用查找和调用远程方法的方法在该类中调用远程方法。
在Java中,只要一个类extends了java.rmi.Remote接口,便可成为存在于服务器端的远程对象,供客户端访问并提供必定的服务
。JavaDoc描述:Remote 接口用于标识其方法能够从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展 java.rmi.Remote 的接口)中指定的这些方法才可远程使用
。
注意:extends了Remote接口的类或者其余接口中的方法如果声明抛出了RemoteException异常,则代表该方法可被客户端远程访问调用
。
同时,远程对象必须实现java.rmi.server.UniCastRemoteObject类
,这样才能保证客户端访问得到远程对象时,该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所得到的这个拷贝称为“存根”,而服务器端自己已存在的远程对象则称之为“骨架”
。其实此时的存根是客户端的一个代理,用于与服务器端的通讯,而骨架也可认为是服务器端的一个代理,用于接收客户端的请求以后调用远程方法来响应客户端的请求。
RMI 框架的基本原理大概以下图,应用了代理模式来封装了本地存根与真实的远程对象进行通讯的细节:
下面给出一个简单的RMI 应用,其中类图以下:其中IService接口用于声明服务器端必须提供的服务(即service()方法),ServiceImpl类是具体的服务实现类,而Server类是最终负责注册服务器远程对象,以便在服务器端存在骨架代理对象来对客户端的请求提供处理和响应。
package com.king.rmi; import java.rmi.Remote; import java.rmi.RemoteException; public interface IService extends Remote { //声明服务器端必须提供的服务 String service(String content) throws RemoteException; }
package com.king.rmi; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; // UnicastRemoteObject用于导出的远程对象和得到与该远程对象通讯的存根。 public class ServiceImpl extends UnicastRemoteObject implements IService { private String name; public ServiceImpl(String name) throws RemoteException { this.name = name; } @Override public String service(String content) { return "server >> " + content; } }
package com.king.rmi; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; public class Server { public static void main(String[] args) { try { // 实例化实现了IService接口的远程服务ServiceImpl对象 IService service02 = new ServiceImpl("service02"); // 本地主机上的远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是1099),必不可缺的一步,缺乏注册表建立,则没法绑定对象到远程注册表上 LocateRegistry.createRegistry(8888); // 把远程对象注册到RMI注册服务器上,并命名为service02 //绑定的URL标准格式为:rmi://host:port/name(其中协议名能够省略,下面两种写法都是正确的) Naming.bind("rmi://localhost:8888/service02",service02); } catch (Exception e) { e.printStackTrace(); } System.out.println("服务器向命名表注册了1个远程服务对象!"); } }
package com.king.rmi; import java.rmi.Naming; public class Client { public static void main(String[] args) { String url = "rmi://localhost:8888/"; try { // 在RMI服务注册表中查找名称为service02的对象,并调用其上的方法 IService service02 =(IService) Naming.lookup(url + "service02"); Class stubClass = service02.getClass(); System.out.println(service02 + " 是 " + stubClass.getName() + " 的实例!"); // 得到本底存根已实现的接口类型 Class[] interfaces = stubClass.getInterfaces(); for (Class c : interfaces) { System.out.println("存根类实现了 " + c.getName() + " 接口!"); } System.out.println(service02.service("你好!")); } catch (Exception e) { e.printStackTrace(); } } }
其实整个简单的RMI 应用中各个类的交互时序以下图: