RMI全称为Remote Method Invocation,翻译成中文是远程方法调用,是JDK1.1提供的面向对象的RPC编程API。
本文以一个简单示例演示RMI的使用方法。
相关类和接口
javax.rmi.Remote
Remote接口用来标识可能被远程虚拟机调用的接口。远程对象必须直接或间接实现这个接口,只有在扩展自Remote的类下声明的方法才可以被远程调用。
javax.rmi.UnicastRemoteObject
UnicastRemoteObject用于按JRMP(Java远程方法协议)导出和回收远程对象,以及获取代理对象。
javax.rmi.LocateRegistry
LocateRegistry用来从特定主机获取一个远程对象的注册表(remote object registry),或是用来在本地特定端口创建一个接受远程调用的远程对象的注册表。(强行翻译API文档)
主要用到一个方法:
方法 | 说明 |
---|---|
static LoacateRegistry createRegistry(int port) | 在特定端口上创建一个接受请求的注册表 |
javax.rmi.Naming
Naming类提供获取和保存远程对象注册表的方法。Naming类中的每个方法都需要一个URL格式的字符串作为name参数,例如//host:port/name
。如果URL没有指定端口号,那么Naming类会默认当作1099端口处理。
主要用到两个方法:
方法 | 说明 | 个人注释 |
---|---|---|
static void bind(String name, Remote obj) | 绑定一个name和一个远程对象 | 其中URL格式的name指明了主机、端口号和命名空间,所以这个方法其实是在对应端口的注册表上声明一个远程对象的意思 |
static Remote lookup(String name) | 返回一个和name相关联的远程对象的代理 | 根据name给出的URL到远程主机上去找对应的注册表,找到要找的远程对象,然后在本地创建它的代理对象 |
使用RMI
实现远程接口
扩展Remote接口
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IMethod extends Remote {
//code参数:用hashCode来标识通信双方来自不同的JVM
void sayHi(int code) throws RemoteException;
}
远程接口实现类
extends UnicastRemoteObject implements Imethod
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class MethodImpl extends UnicastRemoteObject implements IMethod {
protected MethodImpl() throws RemoteException {
}
@Override
public void sayHi(int code) throws RemoteException {
System.out.println("From " + code + " : Hello");
}
}
服务端
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class Server {
public static void main(String[] args) {
System.out.println("Runtime hashCode: " + Runtime.getRuntime().hashCode());
try {
//直接实例化远程对象
IMethod method = new MethodImpl();
//在10086端口上创建一个远程对象注册表
LocateRegistry.createRegistry(10086);
//绑定name和远程对象
Naming.bind("//localhost:10086/method", method);
} catch (RemoteException | MalformedURLException | AlreadyBoundException e) {
e.printStackTrace();
}
}
}
客户端
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class Client {
public static void main(String[] args){
System.out.println("Runtime hashCode: " + Runtime.getRuntime().hashCode());
try {
//获取远程对象代理
IMethod method = (IMethod) Naming.lookup("//localhost:10086/method");
//调用远程过程
method.sayHi(Runtime.getRuntime().hashCode());
} catch (NotBoundException | MalformedURLException | RemoteException e) {
e.printStackTrace();
}
}
}
运行
先运行起服务端程序,再运行客户端
可以看到在服务端的控制台输出了两行
Runtime hashCode : 服务端hashCode
From 客户端hashCode : Hello
同时服务端hashCode和客户端hashCode是不一样的,说明客户端成功地调用了服务端的sayHi方法。
回调方法
在客户端实例化一个远程对象,调用远程方法时作为参数传递给服务端,服务端通过调用这个远程对象的方法实现回调。
在客户端编写一个供回调远程接口
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface ICallback extends Remote {
//供服务端调用,同样code参数用于标识两台
void callback(int code) throws RemoteException;
}
同样的 extends UnicastRemoteObject implements ICallback
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class CallbackImpl extends UnicastRemoteObject implements ICallback {
protected CallbackImpl() throws RemoteException {
}
@Override
public void callback(int code) throws RemoteException {
System.out.println("From " + code + " : Roger that.");
}
}
修改一下上面的接口
增加一个方法call(ICallback callback)
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IMethod extends Remote {
void sayHi(int code) throws RemoteException;
void call(ICallback callback) throws RemoteException;
}
同时修改实现类
... ...
@Override
public void call(ICallback callback) throws RemoteException {
System.out.println("From Client" + " : Hello");
callback.callback(Runtime.getRuntime().hashCode());
}
... ...
客户端调用远程方法
//实例化回调对象
ICallback callback = new CallbackImpl();
//调用远程方法
method.call(callback);
//回收回调对象
UnicastRemoteObject.unexportObject(callback, false);
运行
同样,先运行起服务端,再运行客户端
可以在服务端的控制台看见输出
From Client : Hello
在客户端的控制台看见输出
From 服务端hashCode : Roger that.
说明客户端调用了服务端的call方法,服务端在call方法中调用了客户端传递过来的callback对象的callback方法,并传递过去了本地Runtime的hashCode。
常见的问题
抛出异常
java.rmi.server.ExportException: remote object implements illegal remote interface;
原因:Remote接口中声明的方法必须声明throws RemoteException
java.rmi.server.ExportException: internal error: ObjID already in use
原因:端口被占用或重复在同一端口号创建远程对象注册表
java.net.MalformedURLException: invalid URL scheme
原因:绑定远程对象时的name字符串必须符合URL格式,且前面的协议名必须为rmi://
或者省略
程序不能正常结束
需要回收导出的远程对象,调用UnicastRemoteObject类的方法unexportObject(Remote obj, boolean force)
//回收远程对象
UnicastRemoteObject.unexportObject(object, false);
另外,上文编写远程接口实现类时,可以不使用extends扩展自UnicastRemoteObject。
那么这时候要求这个实现类实现可序列化接口Serializable,并且在绑定端口时通过调用UnicastRemoteObject类的exportObject方法获取这个远程对象的代理对象,再调用Naming.bind进行绑定。
import java.io.Serializable;
import java.rmi.RemoteException;
public class MethodImpl implements IMethod, Serializable {
protected MethodImpl() throws RemoteException {
}
... ...
}
//实例化远程对象
IMethod method = new MethodImpl();
//获取代理对象
Remote obj = UnicastRemoteObject.exportObject(method, 10086);
//绑定端口
Naming.bind("//localhost:10086/method", obj);