一、Java RMI的原理
Java RMI是The Java Remote Method Invocation的简写,其字面含义为:java远程方法调用;Java RMI允许一个JVM上的对象调用另一个JVM中的对象,其使得两个JVM之间可以相互通信,Dubbo则借鉴了这种理念;
RMI可用于构建分布式应用,其原理如下图:

图片来源:https://onclick786.blogspot.com/2016/08/java-remote-method-invocation-or-java.html
1、RMI的基本概念
- Client Host,客户端
- Server Host,服务端
- Server Object,远程对象,存在于服务端,Server Skeleton依赖其做出业务响应;
- Server Stub 对象,客户端本地存根,从远程获取,充当远程服务(
主要为Server Object)的客户端代理; - Server Skeleton 对象,存在于服务端,负责将Server Stub传输过来的数据进行解包,以及调用
Server Object的方法,然后封装返回值给stub对象; - Server Stub 和Server Skeleton 在各自的JVM中使用socket通信将远程调用伪装成本地调用,理解此至关重要,后面将有详解;
- 通过 RMI 注册服务完成服务的注册和发现;
2、调用步骤:
- (1). 服务端(
Server Host)向注册服务注册(RMI Registry Host)自己的地址; - (2). 客户端(
Client Host)通过 RMI 注册服务获取(Look for)目标地址; - (3). 注册中心返回一个本地存根(
Server Stub)给客户端; - (4). 客户端调用本地的 Stub(
即存根对象,Server Stub) 对象上的方法,将请求参数通过网络通信传输给服务端的Server Skeleton - (5). 服务端的 Skeleton(
Server Skeleton) 对象收到网络请求之后,将调用信息解包,然后找到真正的服务对象(Server Object)发起调用,并将返回结果打包通过网络(Data Communication)发送回客户端。
二、Java RMI 使用案例
1. 创建远程对象Server Object
package com.codestack.dubbo.learn.FirstRmi.service;import java.rmi.Remote;import java.rmi.RemoteException;/*** @author Ling* @create 2018-09-03-9:26**/public interface Hello extends Remote{String sayHello(String name) throws RemoteException;}
其中sayHello必须声明RemoteException异常;
Server Object的具体实现:
package com.codestack.dubbo.learn.FirstRmi.service.impl;import com.codestack.dubbo.learn.FirstRmi.service.Hello;/*** @author Ling* @create 2018-09-03-9:26**/public class HelloImpl implements Hello {@Overridepublic String sayHello(String name) {return "hello! " + name;}}
2. 服务注册与绑定
package com.codestack.dubbo.learn.FirstRmi;import com.codestack.dubbo.learn.FirstRmi.service.Hello;import com.codestack.dubbo.learn.FirstRmi.service.impl.HelloImpl;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.rmi.server.UnicastRemoteObject;/*** @author Ling* @create 2018-09-03-9:26**/public class FirstRmiAppServer {public static void main(String[] args) throws RemoteException {//创建SkeletonHello obj = new HelloImpl(); // #1Hello skeleton = (Hello) UnicastRemoteObject.exportObject(obj, 0); // #2//创建本地注册中心Registry registry = LocateRegistry.createRegistry(1099); // #3//绑定skeletonregistry.rebind("Hello", skeleton); // #4}}
3. 客户端注册
package com.codestack.dubbo.learn.FirstRmi;import com.codestack.dubbo.learn.FirstRmi.service.Hello;import java.rmi.NotBoundException;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;/*** @author Ling* @create 2018-09-03-9:26**/public class FirstRmiAppClient {public static void main(String[] args) throws RemoteException, NotBoundException {Registry registry = LocateRegistry.getRegistry(); // #1 获取本地注册中心实例Hello stub = (Hello) registry.lookup("Hello"); // #2 返回一个本地存根String response = stub.sayHello("Chris"); // #3 调用远程方法System.out.println(response);}}
依次运行FirstRmiAppServer,FirstRmiAppClient,控制台将打印:hello! Chris
三、RMI的本质:Stub 和 Skeleton剖析
1. 定义服务端ServerObject
package com.codestack.dubbo.learn.FirstRmi.RmiTheory;public interface Dog {public int weight() throws Throwable;public String watch() throws Throwable;}
实现Dog接口:
package com.codestack.dubbo.learn.FirstRmi.RmiTheory;public class DogServer implements Dog{private int weight;@Overridepublic int weight() throws Throwable {return 100;}@Overridepublic String watch() throws Throwable {return "I'm watching!";}}
2. 定义服务端Skeleton
package com.codestack.dubbo.learn.FirstRmi.RmiTheory;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.net.ServerSocket;import java.net.Socket;public class DogSkeleton extends Thread{private DogServer dogServer;public DogSkeleton(DogServer server){this.dogServer = server;}public void run() {ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(8000);Socket socket = serverSocket.accept();while (!socket.isClosed()) {ObjectInputStream inStream =new ObjectInputStream(socket.getInputStream());//获取客户端Stub的请求方法String method = (String) inStream.readObject();if (method.equals( "weight" )) {//调用ServerObject的真实方法int age = dogServer.weight();ObjectOutputStream outStream =new ObjectOutputStream(socket.getOutputStream());//返回结果给客户端StuboutStream.writeInt(age);outStream.flush();}if (method.equals( "watch" )) {//调用ServerObject的真实方法String name = dogServer.watch();ObjectOutputStream outStream =new ObjectOutputStream(socket.getOutputStream());// return result to stuboutStream.writeObject(name);outStream.flush();socket.close();}}} catch (Exception e) {e.printStackTrace();} catch (Throwable throwable) {throwable.printStackTrace();}}public static void main(String[] args){DogServer dogServer = new DogServer();DogSkeleton dogSkeleton = new DogSkeleton(dogServer);dogSkeleton.start();}}
将DogSkeleton作为外部代理,创建一个socket,等待客户端的连接,根据客户端传入的不通参数,实际调用DogServer来做相应的处理;
3. 定义客户端Stub
package com.codestack.dubbo.learn.FirstRmi.RmiTheory;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.net.Socket;public class DogStub implements Dog{private Socket socket;public DogStub() throws Throwable {socket = new Socket( "localhost" , 8000 );}@Overridepublic int weight() throws Throwable {ObjectOutputStream outStream =new ObjectOutputStream(socket.getOutputStream());outStream.writeObject( "weight" );outStream.flush();ObjectInputStream inStream =new ObjectInputStream(socket.getInputStream());return inStream.readInt();}@Overridepublic String watch() throws Throwable {ObjectOutputStream outStream =new ObjectOutputStream(socket.getOutputStream());outStream.writeObject( "watch" );outStream.flush();ObjectInputStream inStream =new ObjectInputStream(socket.getInputStream());return (String)inStream.readObject();}}
构造DogStub的时候创建一个socket,然后将请求参数写入到ObjectInputStream中,传递给服务端。
4. 测试Demo
package com.codestack.dubbo.learn.FirstRmi.RmiTheory;public class DogClient {public static void main(String[] args) throws Throwable {Dog dogStub = new DogStub();System.out.println("dog's weight is: "+dogStub.weight());System.out.println(dogStub.watch());}}
依次运行:DogSkeleton、DogClient,控制台打印:
dog's weight is: 100I'm watching!
RMI依靠Socket,然后使用Stub和Skeleton对象做通信代理,完成远程调用;
