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命令將服務器端的接口和實體類打包,在客戶端引用,這樣客戶端只有接口,而不會看到具體實現。這裏爲了方便,將接口和實現放在同一個工程。但是更好方式是:
- 將接口寫在一個工程A;
- 將工程A打成jar包;
- 在工程B中引入工程A的jar包,實現這些接口並註冊服務,工程B即服務端;
- 在工程C中引入工程A的jar包,獲取服務並注入給相應接口,工程C即客戶端;
- 客戶端遠程調用。
本例中涉及到的類文件如下:
服務端:
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