1. 简介
Java RMI
指的是远程方法调用 (Remote Method Invocation),是java原生支持的远程调用,采用JRMP(Java Remote Messageing protocol)作为通信协议,可以认为是纯java版本的分布式远程调用解决方案, RMI主要用于不同虚拟机之间的通信,这些虚拟机可以在不同的主机上、也可以在同一个主机上,这里的通信可以理解为一个虚拟机上的对象调用另一个虚拟机上对象的方法。
1.1 客户端
存根/桩(Stub)
:远程对象在客户端上的代理;远程引用层(Remote Reference Layer)
:解析并执行远程引用协议;传输层(Transport)
:发送调用、传递远程方法参数、接收远程方法执行结果。
1.2 服务端
骨架(Skeleton)
:读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值;远程引用层(Remote Reference Layer)
:处理远程引用后向骨架发送远程方法调用;传输层(Transport)
:监听客户端的入站连接,接收并转发调用到远程引用层。
1.3 注册表(Registry)
- 以URL形式注册远程对象,并向客户端回复对远程对象的引用。
1.4 远程调用过程
- 客户端从远程服务器的注册表中查询并获取远程对象引用。
- 桩对象与远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际上是由相应的桩对象代理完成的。
- 远程引用层在将桩的本地引用转换为服务器上对象的远程引用后,再将调用传递给传输层(Transport),由传输层通 过TCP协议发送调用;
- 在服务器端,传输层监听入站连接,它一旦接收到客户端远程调用后,就将这个引用转发给其上层的远程引用层;
- 服务器端的远程引用层将客户端发送的远程应用转换为本地虚拟机的引用后,再将请求传递给骨架(Skeleton);
- 骨架读取参数,又将请求传递给服务器,最后由服务器进行实际的方法调用。
1.5 结果返回过程
- 如果远程方法调用后有返回值,则服务器将这些结果又沿着
“骨架 > 远程引用层 > 传输层”
向下传递; - 客户端的传输层接收到返回值后,又沿着
“传输层 > 远程引用层 > 桩”
向上传递,然后由桩来反序列化这些返回值,并 将最终的结果传递给客户端程序。
1.6 开发流程
服务端
- 定义Remote子接口,在其内部定义要发布的远程方法,并且这些方法都要Throws RemoteException;
- 定义实现远程接口,并且继承 UnicastRemoteObject
- 启动服务器:依次完成注册表的启动和远程对象绑定。
客户端
- 通过符合JRMP规范的URL字符串在注册表中获取并强转成Remote子接口对象;
- 调用这个Remote子接口对象中的某个方法就是为一次远程方法调用行为。
1.7 代码实现
- 创建远程接口
package com.study.service;
import com.study.pojo.User;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IHelloService extends Remote {
/**
* 远程服务对象接口必须继承Remote接口;同时方法必须抛出RemoteExceptino异常
*/
public String sayHello(User user) throws RemoteException;
}
- 其中有一个引用对象作为参数
package com.study.pojo;
import java.io.Serializable;
/** 引用对象应该是可序列化对象,这样才能在远程调用的时候:
* 1. 序列化对象
* 2. 拷贝
* 3. 在网络中传输
* 4. 服务端反序列化
* 5. 获取参数进行方法调用
* 这种方式其实是将远程对象引用传递的方式转化为值传递的方式
*/
public class User implements Serializable {
private String username;
private int age;
public User() {
}
public User(String username, int age) {
this.username = username;
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- 实现远程服务对象
package com.study.service;
import com.study.pojo.User;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 远程服务对象实现类写在服务端;必须继承UnicastRemoteObject或其子类
*/
public class HelloServiceImpl extends UnicastRemoteObject implements IHelloService {
/**
* 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,必须 声明抛出RemoteException异常
*/
public HelloServiceImpl() throws RemoteException {
super();
}
public String sayHello(User user) {
System.out.println("this is server, hello:" + user.getUsername());
return "success";
}
}
- 服务端程序
package com.study.server;
import com.study.service.HelloServiceImpl;
import com.study.service.IHelloService;
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) throws RemoteException, AlreadyBoundException, MalformedURLException {
//1.创建HelloService实例
IHelloService service = new HelloServiceImpl();
//2.获取注册表
LocateRegistry.createRegistry(8889);
//3.对象绑定
//bind方法的参数1: rmi://ip地址:端口/服务名(协议名 rmi 可以省略) 参数2:绑定的对象
Naming.bind("//127.0.0.1:8889/rmiServer",service);
}
}
- 客户端(说明:客户端也要有远程的IHelloService 和 User 作为桩对象)
package com.study.client;
import com.study.pojo.User;
import com.study.service.IHelloService;
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) throws RemoteException, NotBoundException, MalformedURLException {
//1.在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法(强转)
IHelloService service = (IHelloService)Naming.lookup("//127.0.0.1:8889/rmiServer");
//2.准备参数
User user = new User("Eric",18);
//3.调用远程方法sayHello
String msg = service.sayHello(user);
System.out.println(msg);
}
}
- 测试结果