1.RMI概念
在jdk1.8文档中有以下一段话:
RMI的英文全称是Remote Method Invocation,中文解释是远程方法调用。Java RMI能够创建基于Java技术的分布式应用,远程Java对象的方法可以从其他Java虚拟机中调用,也可以是不同的主机。RMI使用对象序列化来编组和解组参数,并且不截断类型,支持真正的面向对象多态性。
2.一个简单的RMI实例
包含5个步骤:
(1)定义远程接口
package gdut.ff.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote{
String sayHello() throws RemoteException;
}
远程对象需要继承接口java.rmi.Remote,并且定义了一系列的方法。每一个远程方法都需要抛出java.rmi.RemoteException。远程方法调用相较于本地方法,会因为很多额外的情况调用失败(例如网络相关连接问题和服务器问题),远程方法会通过抛出java.rmi.RemoteExeption来报告这类问题。
(2)实现服务器
package gdut.ff.rmi;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
/**
* 服务端
* @author liuffei
* @date 2017年9月18日
* @description
*/
public class Server implements Hello{
public Server(){}
public String sayHello(){
return "Hello,World!!!";
}
public static void main(String args[]){
try {
//创建和导出远程对象
Server obj = new Server();
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj,0);
//注册远程对象到Java RMI registry
Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello",stub);
System.err.println("Server ready");
} catch (RemoteException e) {
System.out.println("Server exception:"+e.toString());
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
}
}
“服务器”类,在这个上下文中,是指一个有main方法的类,创建了远程对象实现的实例,导出远程对象,并且绑定了实例的名称到Java RMI registry。包含主方法main的类可以是实现类本身,也可以是其他类。
类Server实现了远程的接口Hello,提供了远程方法sayHello的实现。这个sayHello方法不需要声明抛出任何异常,因为方法实现本身不会抛出RemoteException,也不会抛出任何检查异常。
注意:一个类可以定义不在远程接口的方法,但是这些方法只能在运行服务的虚拟机中调用,不能远程调用。
创建和导出远程对象
Server obj = new Server();
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj,0);
Server的主方法需要创建远程对象来提供服务。另外,远程对象必须导出到Java运行时,以便可以接收到传入的远程调用,做法如上面两行代码。
静态方法UnicastRemoteObject.exportObject输出提供的远程对象来接收匿名TCP端口上的远程方法调用和将来自远程对象的存根传递给客户端。由于exportObject调用的结果,运行时可以开始监听新服务器套接字,或者使用共享服务器套接字来接收远程对象的传入远程调用。返回的存根实现了与远程对象的类相同的远程接口集,并且包含了远程对象可以与之联系的主机名和端口。
注册远程对象到Java RMI registry
对于能在远程对象上调用方法的调用者(client,peer,或者applet),调用者必须首先在远程对象上获取存根。为了引导,Java RMI为应用程序提供了注册表API,用于将名称绑定到远程对象的存根上,而客户端可以通过名称查找远程对象以获得存根。
Java RMI注册表是一个简单的名称服务,允许客户端从远程连接获得引用。通常,注册中心只定位(如果有的话)客户端需要使用的第一个远程对象。然后,典型地,第一个对象会反过来提供特定的应用支持以寻找其他的对象。例如,可以将引用作为参数获取,或者从另一个远程方法调用返回值。
一旦远程对象在服务器上注册,调用者就可以通过名称查找对象,获取远程对象引用,然后调用对象上的远程方法。
服务器中的以下代码在本地主机和默认注册表端口上获得一个存根,然后使用注册表存根将“Hello”绑定到该注册表中的远程对象的存根:
Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello",stub);
静态方法LocateRegistry.getRegistry不带任何参数返回实现了接口java.rmi.registry.Registry的存根和发送了调用到服务器本机的注册表,默认注册端口为1099。bind方法调用registry的存根是为了能够绑定在注册表中名称为”Hello”的远程对象存根。
注意:LocateRegistry.getRegistry的调用为注册表返回适当的存根。这个调用不检查注册表是否真正在运行。绑定方法调用时,如果没有注册表运行在本地服务器的TCP端口1099,服务器会抛出RemoteException。
(3)实现客户端
package gdut.ff.rmi;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Client {
private Client(){}
public static void main(String args[]){
String host = (args.length < 1)?null:args[0];
try {
Registry registry = LocateRegistry.getRegistry(host);
Hello stub = (Hello) registry.lookup("Hello");
String response = stub.sayHello();
System.out.println("response:"+response);
} catch (RemoteException e) {
System.out.println("Client exception:"+e.toString());
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
}
}
客户机程序在服务器主机上获得注册表存根,在注册表中查找远程对象的存根,然后使用存根调用远程对象上的sayHello方法。
客户端首先调用静态LocateRegistry.getRegistry方法使用命令行指定的主机名称获取注册表的存根。如果没有指定的主机名称,null就会作为主机名称,表示本地主机地址会被使用。
接下来,客户端会调用注册表存根中的远程方法lookup去获取服务器注册上远程对象的存根。
然后,客户端调用远程对象存根中的sayHello方法,下面步骤会发生:
a.客户端运行时在远程对象的存根中使用主机和端口信息打开到服务器的连接,然后序列化调用数据。
b.服务器运行时接收传入调用,转发调用给远程对象,并且序列化结果(响应字符串”Hello,World!!!”)给客户端。
c.客户端运行时接收,反序列化,并且返回结果给调用者。
最后将远程对象上的远程调用返回的响应消息打印到System.out。
(4)编译源文件
编译源文件的方法就跟在命令行编译源文件的方法相同。
(5)开启Java RMI registry,server,和client
a.在环境变量的classpath中添加class文件的位置。在命令行中也可以通过set classpath=%classpath%;Server,Client,Hello根目录名;(这一步一定要先于start rmiregistry执行)
注意,上图中的路径是Eclipse项目的文件路径:
E:\Code\day170523\RMIProject\build\classes\gdut\ff\rmi。
gdut/ff/rmi 是项目中的包名。