一、Java RMI的原理

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

RMI可用于构建分布式应用,其原理如下图:

Java RMI - 图1

图片来源: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

  1. package com.codestack.dubbo.learn.FirstRmi.service;
  2. import java.rmi.Remote;
  3. import java.rmi.RemoteException;
  4. /**
  5. * @author Ling
  6. * @create 2018-09-03-9:26
  7. **/
  8. public interface Hello extends Remote{
  9. String sayHello(String name) throws RemoteException;
  10. }

其中sayHello必须声明RemoteException异常;

Server Object的具体实现:

  1. package com.codestack.dubbo.learn.FirstRmi.service.impl;
  2. import com.codestack.dubbo.learn.FirstRmi.service.Hello;
  3. /**
  4. * @author Ling
  5. * @create 2018-09-03-9:26
  6. **/
  7. public class HelloImpl implements Hello {
  8. @Override
  9. public String sayHello(String name) {
  10. return "hello! " + name;
  11. }
  12. }

2. 服务注册与绑定

  1. package com.codestack.dubbo.learn.FirstRmi;
  2. import com.codestack.dubbo.learn.FirstRmi.service.Hello;
  3. import com.codestack.dubbo.learn.FirstRmi.service.impl.HelloImpl;
  4. import java.rmi.RemoteException;
  5. import java.rmi.registry.LocateRegistry;
  6. import java.rmi.registry.Registry;
  7. import java.rmi.server.UnicastRemoteObject;
  8. /**
  9. * @author Ling
  10. * @create 2018-09-03-9:26
  11. **/
  12. public class FirstRmiAppServer {
  13. public static void main(String[] args) throws RemoteException {
  14. //创建Skeleton
  15. Hello obj = new HelloImpl(); // #1
  16. Hello skeleton = (Hello) UnicastRemoteObject.exportObject(obj, 0); // #2
  17. //创建本地注册中心
  18. Registry registry = LocateRegistry.createRegistry(1099); // #3
  19. //绑定skeleton
  20. registry.rebind("Hello", skeleton); // #4
  21. }
  22. }

3. 客户端注册

  1. package com.codestack.dubbo.learn.FirstRmi;
  2. import com.codestack.dubbo.learn.FirstRmi.service.Hello;
  3. import java.rmi.NotBoundException;
  4. import java.rmi.RemoteException;
  5. import java.rmi.registry.LocateRegistry;
  6. import java.rmi.registry.Registry;
  7. /**
  8. * @author Ling
  9. * @create 2018-09-03-9:26
  10. **/
  11. public class FirstRmiAppClient {
  12. public static void main(String[] args) throws RemoteException, NotBoundException {
  13. Registry registry = LocateRegistry.getRegistry(); // #1 获取本地注册中心实例
  14. Hello stub = (Hello) registry.lookup("Hello"); // #2 返回一个本地存根
  15. String response = stub.sayHello("Chris"); // #3 调用远程方法
  16. System.out.println(response);
  17. }
  18. }

依次运行FirstRmiAppServerFirstRmiAppClient,控制台将打印:hello! Chris

三、RMI的本质:Stub 和 Skeleton剖析

1. 定义服务端ServerObject

  1. package com.codestack.dubbo.learn.FirstRmi.RmiTheory;
  2. public interface Dog {
  3. public int weight() throws Throwable;
  4. public String watch() throws Throwable;
  5. }

实现Dog接口:

  1. package com.codestack.dubbo.learn.FirstRmi.RmiTheory;
  2. public class DogServer implements Dog{
  3. private int weight;
  4. @Override
  5. public int weight() throws Throwable {
  6. return 100;
  7. }
  8. @Override
  9. public String watch() throws Throwable {
  10. return "I'm watching!";
  11. }
  12. }

2. 定义服务端Skeleton

  1. package com.codestack.dubbo.learn.FirstRmi.RmiTheory;
  2. import java.io.IOException;
  3. import java.io.ObjectInputStream;
  4. import java.io.ObjectOutputStream;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. public class DogSkeleton extends Thread{
  8. private DogServer dogServer;
  9. public DogSkeleton(DogServer server){
  10. this.dogServer = server;
  11. }
  12. public void run() {
  13. ServerSocket serverSocket = null;
  14. try {
  15. serverSocket = new ServerSocket(8000);
  16. Socket socket = serverSocket.accept();
  17. while (!socket.isClosed()) {
  18. ObjectInputStream inStream =
  19. new ObjectInputStream(socket.getInputStream());
  20. //获取客户端Stub的请求方法
  21. String method = (String) inStream.readObject();
  22. if (method.equals( "weight" )) {
  23. //调用ServerObject的真实方法
  24. int age = dogServer.weight();
  25. ObjectOutputStream outStream =
  26. new ObjectOutputStream(socket.getOutputStream());
  27. //返回结果给客户端Stub
  28. outStream.writeInt(age);
  29. outStream.flush();
  30. }
  31. if (method.equals( "watch" )) {
  32. //调用ServerObject的真实方法
  33. String name = dogServer.watch();
  34. ObjectOutputStream outStream =
  35. new ObjectOutputStream(socket.getOutputStream());
  36. // return result to stub
  37. outStream.writeObject(name);
  38. outStream.flush();
  39. socket.close();
  40. }
  41. }
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. } catch (Throwable throwable) {
  45. throwable.printStackTrace();
  46. }
  47. }
  48. public static void main(String[] args){
  49. DogServer dogServer = new DogServer();
  50. DogSkeleton dogSkeleton = new DogSkeleton(dogServer);
  51. dogSkeleton.start();
  52. }
  53. }

将DogSkeleton作为外部代理,创建一个socket,等待客户端的连接,根据客户端传入的不通参数,实际调用DogServer来做相应的处理;

3. 定义客户端Stub

  1. package com.codestack.dubbo.learn.FirstRmi.RmiTheory;
  2. import java.io.ObjectInputStream;
  3. import java.io.ObjectOutputStream;
  4. import java.net.Socket;
  5. public class DogStub implements Dog{
  6. private Socket socket;
  7. public DogStub() throws Throwable {
  8. socket = new Socket( "localhost" , 8000 );
  9. }
  10. @Override
  11. public int weight() throws Throwable {
  12. ObjectOutputStream outStream =
  13. new ObjectOutputStream(socket.getOutputStream());
  14. outStream.writeObject( "weight" );
  15. outStream.flush();
  16. ObjectInputStream inStream =
  17. new ObjectInputStream(socket.getInputStream());
  18. return inStream.readInt();
  19. }
  20. @Override
  21. public String watch() throws Throwable {
  22. ObjectOutputStream outStream =
  23. new ObjectOutputStream(socket.getOutputStream());
  24. outStream.writeObject( "watch" );
  25. outStream.flush();
  26. ObjectInputStream inStream =
  27. new ObjectInputStream(socket.getInputStream());
  28. return (String)inStream.readObject();
  29. }
  30. }

构造DogStub的时候创建一个socket,然后将请求参数写入到ObjectInputStream中,传递给服务端。

4. 测试Demo

  1. package com.codestack.dubbo.learn.FirstRmi.RmiTheory;
  2. public class DogClient {
  3. public static void main(String[] args) throws Throwable {
  4. Dog dogStub = new DogStub();
  5. System.out.println("dog's weight is: "+dogStub.weight());
  6. System.out.println(dogStub.watch());
  7. }
  8. }

依次运行:DogSkeletonDogClient,控制台打印:

  1. dog's weight is: 100
  2. I'm watching!

RMI依靠Socket,然后使用Stub和Skeleton对象做通信代理,完成远程调用;