1. Java RMI
Java RMI(Java Remote Method Invocation),即Java远程方法调用,是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它的实现依赖于Java虚拟机(JVM),因此它仅支持从一个JVM到另一个JVM的调用。这些JVM可以在不同的主机上、也可以在同一个主机上。
RMI 使用 JRMP(Java Remote Message Protocol,Java远程消息交换协议)实现,本质是TCP网络通信,内部封装了序列化和通信过程,通过代理使得客户端运行的程序可以调用远程服务器上的对象。JRMP是实现RPC(Remote Procedure Call,远程过程调用)的一种方式。远程调用可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式对象间的通讯。
Java RMI 由三个部分构成:
- rmiregistry(JDK提供的一个可以独立运行的程序,在bin目录下);
- server端的程序,对外提供远程对象;
- client端的程序,想要调用远程对象的方法。
2. 使用步骤
- 先启动rmiregistry服务,启动时可以指定服务监听的端口,也可以使用默认的端口(1099)。
- server端在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming/Context/Registry等类的bind或rebind方法将刚才实例化好的实现类注册到rmiregistry上并对外暴露一个名称。
- client端通过本地的接口和一个已知的名称(即rmiregistry暴露出的名称),再使用RMI提供的Naming/Context/Registry等类的lookup方法从RMIService那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,便可以实现远程调用对象的方法了。
3. 存根和骨干网通信过程
方法调用从客户对象经存根(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传输层,向上穿过远程引用层和骨干网(Skeleton),到达服务器对象。
存根:扮演着远程服务器对象的代理的角色,使该对象可被客户激活。
远程引用层:处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。
传输层:管理实际的连接,并且追踪可以接受方法调用的远程对象。
骨干网:完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。
4. 使用 Naming 实现 RMI
UserService 继承 Remote 接口,标识可以从非本地虚拟机上调用的接口,成为存在于服务器端的远程对象,任何远程对象都必须直接或间接实现此接口。供客户端访问并提供一定的服务,接口中方法要声明抛出了RemoteException异常,对象需要序列化。
public interface UserService extends Remote {
String getName() throws RemoteException;
void setName(String name) throws RemoteException;
Integer getAge() throws RemoteException;
void setAge(Integer age) throws RemoteException;
void sayHello() throws RemoteException;
void sayHello(String name) throws RemoteException;
void sayHello(String name, Integer age) throws RemoteException;
void getHashCode() throws RemoteException;
}
远程对象必须继承 UniCastRemoteObject 类,保证客户端访问获得远程对象时,该远程对象将会把自身的一个拷贝以 Socket 的形式传输给客户端。
此时客户端所获得的这个拷贝称为“存根”(stub),而服务器端本身已存在的远程对象则称之为“骨架”(skeleton)。
此时的存根是客户端的一个代理,用于与服务器端的通信,而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。
public class User extends UnicastRemoteObject implements UserService {
private String name;
private Integer age;
// 构造函数中将生成stub和skeleton
public User() throws RemoteException{
super();
}
@Override
public String getName() throws RemoteException {
return name;
}
@Override
public void setName(String name) throws RemoteException {
this.name = name;
}
@Override
public Integer getAge() throws RemoteException {
return age;
}
@Override
public void setAge(Integer age) throws RemoteException {
this.age = age;
}
@Override
public void sayHello() throws RemoteException {
System.out.println("sayHello() hello " + name + " " + age);
}
@Override
public void sayHello(String name) throws RemoteException {
System.out.println("sayHello(String name) hello " + name);
}
@Override
public void sayHello(String name, Integer age) throws RemoteException {
System.out.println("sayHello(String name, Integer age) hello " + name + " " + age);
}
@Override
public void getHashCode() throws RemoteException {
System.out.println("hashCode " + this.hashCode());
}
}
注册远程对象,向客户端提供远程对象服务。远程对象是在远程服务上创建的,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了。
public class UserRmiServer {
public static void main(String[] args) throws Exception {
User user = new User();
// 创建本地主机上的远程对象注册表Registry的实例,默认端口1099
LocateRegistry.createRegistry(10001);
Naming.bind("rmi://127.0.0.1:10001/rmiUser", user);
}
}
从 Registry 中检索远程对象的存根(stub),通过stub调用远程接口实现
public class UserRmiClient {
public static void main(String[] args) throws Exception {
UserService userService = (UserService) Naming.lookup("rmi://127.0.0.1:10001/rmiUser");
userService.sayHello();
userService.setName("张三");
userService.setAge(20);
System.out.println("name=" + userService.getName() + ";age=" + userService.getAge());
userService.sayHello();
userService.sayHello("李四");
userService.sayHello("王五", 30);
userService.getHashCode();
}
}
测试
5. 优缺点
优点
给分布计算的系统设计、编程都带来了极大的方便。只要按照RMI规则设计程序,可以不必再过问在RMI之下的网络细节了,如:TCP和Socket等等。任意两台计算机之间的通讯完全由RMI负责。调用远程计算机上的对象就像本地对象一样方便。
缺点
RMI对服务器的IP地址和端口依赖很紧密。RMI是Java语言的远程调用,两端的程序语言必须是Java实现,Scala和Java可以互通。
参考:
Java文档 RMI教程
理解Java RMI 一篇就够
分布式架构基础:Java RMI详解
JAVA RMI 原理和使用浅析
java RMI原理详解
从懵逼到恍然大悟之Java中RMI的使用