JDK RMI 探索与使用 -- 序列化

在 RMI 通信模型中有两个重要问题,一是远程对象的发现的问题,二是数据的传递问题。在 java 程序中,数据类型包括基本数据类型和引用数据类型,而 RMI 通信主要是引用类型的使用和传递。

引用类型的传递方式,对于在同一 JVM 中的传递时,因为参数的引用和程序同属于一个内存,传递起来没有问题,但是不同 JVM,一个 jvmA 对象引用使用另外一个 jvmB 中的 class 文件进行实例化,不大可能,RMI 是将对象在 jvmB 中实例化,并将对象发布到注册中心,当 jvmA 客户端调用的远程对象复制到本地时,通过注册中心找到远程对象在 jvmB 中的引用,并通过建立 socket 的方式进行对象数据的复制传输。对象数据的传输,需要将对象序列化为字节,然后使用该字节的副本在 C/S 之间传递。

在 java 中,一个对象如果能够被序列化,需要满足下面两个条件之一
①java 基本数据类型;
②实现 java.io.Serializable 接口

如果存在嵌套对象,嵌套的对象也要是可以序列化的,除非被标识成不用序列化

通过查看代码,可知 RMI 是使用对象流 IO 类 ObjectOutputStream 和 ObjectInputStream 来实现对象的序列化传输,该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作,而需要被序列化和反序列化的类必须实现 Serializable 接口。

可以通过一个例子先了解下,这个例子是是将一组 Person 对象通过 ObjectOutputStream 的方式存入本地磁盘文件中,然后通过 ObjectInputStream 的方式将文件中的对象读取到程序里。

首先创建一个 Person 类,如下:

import java.io.Serializable;

public class Person implements Serializable {
private String name;
    private int age;
    public Person(String name, int age) {
this.name = name;
        this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name="+ name + ", age="+ age + "]";
}
}

Person 类需要实现 Serializable 接口,否则会出现 java.io.NotSerializableException 异常。
接下来我们通过对象流 IO 类 ObjectOutputStream 和 ObjectInputStream 来实现对对象实例数据的操作。

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class ObjectIOTest {
public static void main(String[] args) {
writeObject();
getObject();
}
private static void writeObject() {
try {
            List<Person> persons = new ArrayList<>();
persons.add(new Person("zhangsan", 24));
persons.add(new Person("lisi", 25));
persons.add(new Person("wanger", 26));
persons.add(new Person("gouzi", 27));
persons.add(new Person("huizi", 28));
ObjectOutputStream stream =new ObjectOutputStream(
new FileOutputStream(
"D:\\opensource\\javabase\\javarmi\\src\\main\\java\\com\\para\\ser\\person.txt"));
stream.writeObject(persons);
} catch (IOException e) {
            e.printStackTrace();
}
    }
private static void getObject() {
try {
            ObjectInputStream stream = new ObjectInputStream(new FileInputStream("D:\\opensource\\javabase\\javarmi\\src\\main\\java\\com\\para\\ser\\person.txt"));
List<Person> personList = (List) stream.readObject();
            if (personList != null && personList.size() >0) {
for(int i = 0; i < personList.size(); i ++) {
                    System.out.println(personList.get(i));
}
            }
        } catch (IOException e) {
            e.printStackTrace();
} catch (ClassNotFoundException e) {
            e.printStackTrace();
}
    }
}

输出结果如下:

Person [name=zhangsan, age=24]
Person [name=lisi, age=25]
Person [name=wanger, age=26]
Person [name=gouzi, age=27]
Person [name=huizi, age=28]

上面的例子是通过本地磁盘文件作为通信的方式,RMI 是通过 Socket 的方式,Skeleton 对象做的事情是将服务实现传入构造参数,获取 Stub 客户端通过 socket 传过来的方法调用字符串标识,将请求转发到具体的服务上面,获取结果之后返回给客户端。我们可以简单实现一下。
首先是 Stub 类

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

public class ObjectStub {

private Socket socket;

    public ObjectStub() throws Throwable {
socket = new Socket("localhost", 8888);
}

public Object getObject()  throws Throwable {
        ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream());
outStream.writeObject("getPerson");
outStream.flush();
ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream());
        return inStream.readObject();
}

public static void main(String[] args) {
try {
            ObjectStub stub = new ObjectStub();
Object o = stub.getObject();
            if(o instanceof Person) {
                System.out.println("获取对象成功:"+ o);
} else  {
                System.out.println("获取对象失败:"+ o);
}
        } catch (Throwable throwable) {
            throwable.printStackTrace();
}
    }
}

然后是 Skeleton 类

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ObjectSkeleton implements Runnable {

@Override
public void run() {
try {
            ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();

            while (socket != null) {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos =  new ObjectOutputStream(socket.getOutputStream());
String action = (String)ois.readObject();
                if(action.equals("getPerson")) {
                    Person person = new Person("zhangsan", 24);
oos.writeObject(person);
oos.flush();
} else {
                    oos.writeObject("error");
oos.flush();
}
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
System.exit(0);
}
    }
public static void main(String[] args) {
new Thread(new ObjectSkeleton()).start();
}
}

上面简单实现了如何通过客户端如何通过指定参数获取服务端的对象的例子。这里面只是获取 Person 一个类,如果是很多个对象,不能每个都写一个参数获取,RMI 的方式是要求每个需要远程调用的类都继承 Remote 类,从服务端获取的都是 Remote 的子类,是典型的面向对象的编程方式。

本文简单介绍了 RMI 的序列化传递引用类型的方式,如果想了解更多,可以查阅更多资料,或者加入开测,一起讨论学习。

(1)https://blog.csdn.net/chenshun123/article/details/79518105
(2)https://blog.csdn.net/sinat_34596644/article/details/52599688


↙↙↙阅读原文可查看相关链接,并与作者交流