java序列化实现RMI

RMI(Remote Method Invocation)是Java中的远程过程调用(Remote Procedure Call,RPC)实现,是一种分布式Java应用的实现方式。它的目的在于对开发人员屏蔽横跨不同JVM和网络连接等细节,使得分布在不同JVM上的对象像是存在于一个统一的JVM中一样,可以很方便的互相通讯。通讯就涉及到了数据的编码和解码,对于一般的数据类型我们不需要这么做,但是涉及到比较复杂的数据类型,例如对象,RMI利用到了序列化,使得数据的编码与解码对于开发人员透明起来,我们不需要关注数据如何传输,只需要实现相关方法就能做到远程调用。

一、序列化

对象的序列化主要是使用JAVA中的ObjectOutputStream的writeObject(Object obj)来序列化和ObjectInputStream的readObject()来反序列化。
1、在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。当然目前大多数项目存储会员状态信息都会用到Memcached。
2、当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。此处RMI就利用了这点来帮我们编排对象数据信息。

总结就有如下优点:

  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
  • 便于在网络上传送对象的字节序列。
package rmiTest;

import java.io.Serializable;

public class Person implements Serializable{

    private static final long serialVersionUID = -6379887343250921447L;

    private int id;
    private String name;
    private String sex;

    public Person(){}

    public Person(int id, String name, String sex) {
        super();
        this.id = id;
        this.name = name;
        this.sex = sex;
    }
    //getXx();
    //setXx();
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
package serializeTest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import rmiTest.Person;

public class TestObjSerializeAndDeserialize {

    public static void main(String[] args) {
        Person person = new Person(1, "丁烁", "man");
        try {
            serialize(person, "E:/ds.txt");
            deserialize("E:/ds.txt");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //序列化
    public static void serialize(Object object, String path) throws Exception{
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(path));
        outputStream.writeObject(object);
        outputStream.flush();
        outputStream.close();
        System.out.println("序列化成功!");
    }
    //反序列化
    public static void deserialize(String path) throws Exception{
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(path));
        Person person =(Person) inputStream.readObject();
        inputStream.close();
        System.out.println("反序列化成功!");
        System.out.println("id:"+person.getId());
        System.out.println("name:"+person.getName());
        System.out.println("sex:"+person.getSex());
    }

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

输出结果:
结果输出
文件生成:
生成的文件

注意事项:实体类上必须实现序列化接口,并且设定一个serialVersionUID。如果没有添加该字段,序列化对象之后,修改实体类再进行反序列化就会报错。java编译器会自动给这个class进行一个摘要算法来生成serialVersionUID,只要这个文件多一个空格,得到的serialVersionUID就会截然不同。所以修改之后java编译器又给我们生成了一个serialVersionUID,反序列化时两个版本号不一致就会报错。

二、jar命令

jar(Java archive file),这里为了更加真实的模拟远程接口调用,我们使用一个简单的面向接口编程,包含一个客户端和一个服务端。服务端包含了接口、实现以及实体类,客户端引入接口以及实体类的jar包,通过远程接口调用来调用接口的实现类。所以简要的梳理一下如何使用jar命令来进行打包相关类文件。当然在实际的项目中会使用maven来构建jar工程。

举例:
jar cvf d:\rmitest.jar D:\workspace\tjfae-v2300-20160410\appTest\bin\rmiTest
该命令会将rmiTest下的所有文件打包成jar并命名为rmitest.jar存放与d盘下。当然也可以指定多个目录、多个类文件。但是这样有一个弊端,我们打出的jar并不是按照我们所想的package目录打出来的。我们反编译一个jar如下图:
这里写图片描述
jar里面的类文件路径是按照我们打包时的路径所写,这样会导致我们在引用这个jar包时,报路径错误。明白了这点,我们可以将dos下的当前目录调整到与package对应位置,再执行打包命令。先将dos路径调整到D:\workspace\tjfae-v2300-20160410\appTest\bin下,然后执行如下命令:
jar cvf d:\rmitest.jar rmiTest
这里写图片描述

此时就已经打出了我们想要的jar包,为后面的远程接口调用做准备。

三、RMI远程调用实例

前面介绍了序列化和如何利用jar命令打包,下面的例子分为客户端和服务器端,服务器端包含了接口和接口的实现,并且将实现注册到一个服务地址上。然后用jar命令将服务器端的接口和实体类打包,在客户端引用,这样客户端只有接口,而不会看到具体实现。这里为了方便,将接口和实现放在同一个工程。但是更好方式是:

  1. 将接口写在一个工程A;
  2. 将工程A打成jar包;
  3. 在工程B中引入工程A的jar包,实现这些接口并注册服务,工程B即服务端;
  4. 在工程C中引入工程A的jar包,获取服务并注入给相应接口,工程C即客户端;
  5. 客户端远程调用。

本例中涉及到的类文件如下:
服务端:
Person:实体类,实现Serializable进行序列化;
PersonService:服务接口,继承Remote;
PersonServiceImp:服务实现,继承UnicastRemoteObject,并且实现PersonService;
ServerTest:注册PersonService这个服务;
客户端:
引入事先打好的jar包(Person.class、PersonService.class)
ClientTest:调用远程服务。

Person上面已经贴出。
PersonService包含一个getPerson的抽象方法。

package rmiTest;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface PersonService extends Remote {

    public Person getPerson() throws RemoteException;

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

PersonServiceImp必须要继承UnicastRemoteObject,如果不继承这个类,那么客户端每次lookup出来的都是不一样的对象,可以通过在服务端添加一个成员变量,每调用一次自增一下来进行测试,这里不做多余讲解。

package rmiTest;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class PersonServiceImp extends UnicastRemoteObject implements PersonService {

    private static final long serialVersionUID = 1L;

    protected PersonServiceImp() throws RemoteException {
        super();
    }

    @Override
    public Person getPerson() throws RemoteException {
        Person person = new Person();
        person.setId(1);
        person.setName("ds");
        person.setSex("M");
        return person;
    }

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

ServerTest服务端注册这个服务

package rmiTest;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class ServerTest {

    public static void main(String[] arg){
        try {
            PersonService personService = new PersonServiceImp();
            LocateRegistry.createRegistry(6600);
            Naming.bind("rmi://127.0.0.1:6600/PersonService", personService);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

ClientTest客户端进行远程调用,客户端要先引入事先打好的jar包


import java.rmi.Naming;

import rmiTest.Person;
import rmiTest.PersonService;

public class ClientTest {

    public static void main(String[] args) {
        try {
            PersonService personService = (PersonService) Naming.lookup("rmi://127.0.0.1:6600/PersonService");
            Person person = personService.getPerson();
            System.out.println("ID:"+person.getId());
            System.out.println("Name:"+person.getName());
            System.out.println("Sex:"+person.getSex());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
(function () {('pre.prettyprint code').each(function () { var lines = (this).text().split(\n).length;var numbering = $('
    ').addClass('pre-numbering').hide(); (this).addClass(hasnumbering).parent().append( numbering); for (i = 1; i
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值