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
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章