java RMI原理詳解

轉載: http://blog.csdn.net/xinghun_4/article/details/45787549

定義

RMI(Remote Method Invocation)爲遠程方法調用,是允許運行在一個Java虛擬機的對象調用運行在另一個Java虛擬機上的對象的方法。 這兩個虛擬機可以是運行在相同計算機上的不同進程中,也可以是運行在網絡上的不同計算機中。

Java RMI:Java遠程方法調用,即java RMI(Java Remote Method Invocation)是Java編程語言裏,一種用於實現遠程過程調用的應用程序編程接口。它使客戶機上運行的程序可以調用遠程服務器上的對象。遠程方法調用特性使Java編程人員能夠在網絡環境中分佈操作。RMI全部的宗旨就是儘可能簡化遠程接口對象的使用。

我們知道遠程過程調用(Remote Procedure Call, RPC)可以用於一個進程調用另一個進程(很可能在另一個遠程主機上)中的過程,從而提供了過程的分佈能力。Java 的 RMI 則在 RPC 的基礎上向前又邁進了一步,即提供分佈式對象間的通訊。

本地對象調用

我們先看看本地對象方法的調用:

  1. ObjectClass objectA = new ObjectClass();    
  2. String retn = objectA.Method();    
ObjectClass objectA = new ObjectClass();  
String retn = objectA.Method();  

但是想想,如果objectA對象在JVM a上;而我們的程序在JVM b上,而且想訪問JVM a上的objectA對象方法,如何做呢?對於JVM b上的應用程序來說,是不知道JVM a上創建的ObjectClass實例對象名稱是什麼,因爲這次我創建的實例對象可能是objectA,下次我程序一改,我創建的實例對象又叫objectB了,另外,我創沒創建ObjectClass實例對象,JVM b上應用程序又怎麼知道呢?

RMI就解決了這個問題。

工作原理


方法調用從客戶對象經佔位程序(Stub)、遠程引用層(Remote Reference Layer)和傳輸層(Transport Layer)向下,傳遞給主機,然後再次經傳 輸層,向上穿過遠程調用層和骨幹網(Skeleton),到達服務器對象。 佔位程序扮演着遠程服務器對象的代理的角色,使該對象可被客戶激活。 遠程引用層處理語義、管理單一或多重對象的通信,決定調用是應發往一個服務器還是多個。傳輸層管理實際的連接,並且追蹤可以接受方法調用的遠程對象。服務器端的骨幹網完成對服務器對象實際的方法調用,並獲取返回值。返回值向下經遠程引用層、服務器端的傳輸層傳遞迴客戶端,再向上經傳輸層和遠程調用層返回。最後,佔位程序獲得返回值。 
要完成以上步驟需要有以下幾個步驟: 
1、 生成一個遠程接口 
2、 實現遠程對象(服務器端程序)
3、 生成佔位程序和骨幹網(服務器端程序)
4、 編寫服務器程序 
5、 編寫客戶程序 
6、 註冊遠程對象 
7、 啓動遠程對象  

由於有RMI系統的支持,我們寫RMI應用程序時只需要繼承相關類,實現相關接口就可以了。也就是說,我們只需要定義接口、接口實現、客戶端程序和服務端程序就可以了。


上圖中的stub和skeleton代理都是在服務端程序中由RMI系統動態生成,服務端程序只需要繼承java.rmi.server.UnicastRemoteObject類。

那麼上圖中的RMI Service(RMI registry)是怎麼回事呢?

先賣個關子:

可以說,RMI由3個部分構成,第一個是RMIService即JDK提供的一個可以獨立運行的程序(bin目錄下的rmiregistry),第二個是RMIServer即我們自己編寫的一個java項目,這個項目對外提供服務。第三個是RMIClient即我們自己編寫的另外一個java項目,這個項目遠程使用RMIServer提供的服務。
首先,RMIService必須先啓動並開始監聽對應的端口。
其次,RMIServer將自己提供的服務的實現類註冊到RMIService上,並指定一個訪問的路徑(或者說名稱)供RMIClient使用。
最後,RMIClient使用事先知道(或和RMIServer約定好)的路徑(或名稱)到RMIService上去尋找這個服務,並使用這個服務在本地的接口調用服務的具體方法。
通俗的講完了再稍微技術的講下:
首先,在一個JVM中啓動rmiregistry服務,啓動時可以指定服務監聽的端口,也可以使用默認的端口。
其次,RMIServer在本地先實例化一個提供服務的實現類,然後通過RMI提供的Naming,Context,Registry等類的bind或rebind方法將剛纔實例化好的實現類註冊到RMIService上並對外暴露一個名稱。
最後,RMIClient通過本地的接口和一個已知的名稱(即RMIServer暴露出的名稱)再使用RMI提供的Naming,Context,Registry等類的lookup方法從RMIService那拿到實現類。這樣雖然本地沒有這個類的實現類,但所有的方法都在接口裏了,想怎麼調就怎麼調吧。
值得注意的是理論上講RMIService,RMIServer,RMIClient可以部署到3個不同的JVM中,這個時候的執行的順序是RMIService—RMIServer—RMIClient。另外也可以由RMIServer來啓動RMIService這時候執行的順序是RMIServer—RMIService—RMIClient。

實際應用中很少有單獨提供一個RMIService服務器,開發的時候可以使用Registry類在RMIServer中啓動RMIService。

Java RMI 簡單示例

1. 定義一個遠程接口

  1. /* IHello.java */  
  2. package mytest;  
  3. /*  
  4.  * 在Java中,只要一個類extends了java.rmi.Remote接口,即可成爲存在於服務器端的遠程對象,  
  5.  * 供客戶端訪問並提供一定的服務。JavaDoc描述:Remote 接口用於標識其方法可以從非本地虛擬機上  
  6.  * 調用的接口。任何遠程對象都必須直接或間接實現此接口。只有在“遠程接口”  
  7.  * (擴展 java.rmi.Remote 的接口)中指定的這些方法纔可被遠程調用。  
  8.  */  
  9. import java.rmi.Remote;  
  10.   
  11. public interface IHello extends Remote {  
  12.     /* extends了Remote接口的類或者其他接口中的方法若是聲明拋出了RemoteException異常, 
  13.      * 則表明該方法可被客戶端遠程訪問調用。 
  14.      */  
  15.     public String sayHello(String name) throws java.rmi.RemoteException;  
  16. }  
/* IHello.java */
package mytest;
/* 
 * 在Java中,只要一個類extends了java.rmi.Remote接口,即可成爲存在於服務器端的遠程對象, 
 * 供客戶端訪問並提供一定的服務。JavaDoc描述:Remote 接口用於標識其方法可以從非本地虛擬機上 
 * 調用的接口。任何遠程對象都必須直接或間接實現此接口。只有在“遠程接口” 
 * (擴展 java.rmi.Remote 的接口)中指定的這些方法纔可被遠程調用。 
 */
import java.rmi.Remote;

public interface IHello extends Remote {
    /* extends了Remote接口的類或者其他接口中的方法若是聲明拋出了RemoteException異常,
     * 則表明該方法可被客戶端遠程訪問調用。
     */
    public String sayHello(String name) throws java.rmi.RemoteException;
}


2. 遠程接口實現類

  1. /* HelloImpl.java */  
  2. package mytest;  
  3. import java.rmi.RemoteException;  
  4. import java.rmi.server.UnicastRemoteObject;  
  5.   
  6. /* 
  7.  * 遠程對象必須實現java.rmi.server.UniCastRemoteObject類,這樣才能保證客戶端訪問獲得遠程對象時, 
  8.  * 該遠程對象將會把自身的一個拷貝以Socket的形式傳輸給客戶端,此時客戶端所獲得的這個拷貝稱爲“存根”, 
  9.  * 而服務器端本身已存在的遠程對象則稱之爲“骨架”。其實此時的存根是客戶端的一個代理,用於與服務器端的通信, 
  10.  * 而骨架也可認爲是服務器端的一個代理,用於接收客戶端的請求之後調用遠程方法來響應客戶端的請求。 
  11.  */   
  12.   
  13. /* java.rmi.server.UnicastRemoteObject構造函數中將生成stub和skeleton */  
  14. public class HelloImpl extends UnicastRemoteObject implements IHello {  
  15.     // 這個實現必須有一個顯式的構造函數,並且要拋出一個RemoteException異常    
  16.     protected HelloImpl() throws RemoteException {  
  17.         super();  
  18.     }  
  19.       
  20.     private static final long serialVersionUID = 4077329331699640331L;  
  21.     public String sayHello(String name) throws RemoteException {  
  22.         return “Hello ” + name + “ ^_^ ”;  
  23.     }  
  24. }  
/* HelloImpl.java */
package mytest;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/*
 * 遠程對象必須實現java.rmi.server.UniCastRemoteObject類,這樣才能保證客戶端訪問獲得遠程對象時,
 * 該遠程對象將會把自身的一個拷貝以Socket的形式傳輸給客戶端,此時客戶端所獲得的這個拷貝稱爲“存根”,
 * 而服務器端本身已存在的遠程對象則稱之爲“骨架”。其實此時的存根是客戶端的一個代理,用於與服務器端的通信,
 * 而骨架也可認爲是服務器端的一個代理,用於接收客戶端的請求之後調用遠程方法來響應客戶端的請求。
 */ 

/* java.rmi.server.UnicastRemoteObject構造函數中將生成stub和skeleton */
public class HelloImpl extends UnicastRemoteObject implements IHello {
    // 這個實現必須有一個顯式的構造函數,並且要拋出一個RemoteException異常  
    protected HelloImpl() throws RemoteException {
        super();
    }

    private static final long serialVersionUID = 4077329331699640331L;
    public String sayHello(String name) throws RemoteException {
        return "Hello " + name + " ^_^ ";
    }
}


3. 服務端

  1. /* HelloServer.java */  
  2. package mytest;  
  3. import java.rmi.registry.LocateRegistry;  
  4. import java.rmi.registry.Registry;  
  5.   
  6. /* 註冊遠程對象,向客戶端提供遠程對象服務  
  7.  * 遠程對象是在遠程服務上創建的,你無法確切地知道遠程服務器上的對象的名稱 
  8.  * 但是,將遠程對象註冊到RMI Service之後,客戶端就可以通過RMI Service請求 
  9.  * 到該遠程服務對象的stub了,利用stub代理就可以訪問遠程服務對象了 
  10.  */  
  11.    
  12. public class HelloServer {  
  13.     public static void main(String[] args) {  
  14.         try {  
  15.             IHello hello = new HelloImpl(); /* 生成stub和skeleton,並返回stub代理引用 */  
  16.             /* 本地創建並啓動RMI Service,被創建的Registry服務將在指定的端口上偵聽到來的請求  
  17.              * 實際上,RMI Service本身也是一個RMI應用,我們也可以從遠端獲取Registry: 
  18.              *     public interface Registry extends Remote; 
  19.              *     public static Registry getRegistry(String host, int port) throws RemoteException; 
  20.              */  
  21.             LocateRegistry.createRegistry(1099);  
  22.             /* 將stub代理綁定到Registry服務的URL上 */  
  23.             java.rmi.Naming.rebind(”rmi://localhost:1099/hello”, hello);  
  24.             System.out.print(”Ready”);  
  25.         } catch (Exception e) {  
  26.             e.printStackTrace();  
  27.         }  
  28.     }  
  29. }  
/* HelloServer.java */
package mytest;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/* 註冊遠程對象,向客戶端提供遠程對象服務 
 * 遠程對象是在遠程服務上創建的,你無法確切地知道遠程服務器上的對象的名稱
 * 但是,將遠程對象註冊到RMI Service之後,客戶端就可以通過RMI Service請求
 * 到該遠程服務對象的stub了,利用stub代理就可以訪問遠程服務對象了
 */

public class HelloServer {
    public static void main(String[] args) {
        try {
            IHello hello = new HelloImpl(); /* 生成stub和skeleton,並返回stub代理引用 */
            /* 本地創建並啓動RMI Service,被創建的Registry服務將在指定的端口上偵聽到來的請求 
             * 實際上,RMI Service本身也是一個RMI應用,我們也可以從遠端獲取Registry:
             *     public interface Registry extends Remote;
             *     public static Registry getRegistry(String host, int port) throws RemoteException;
             */
            LocateRegistry.createRegistry(1099);
            /* 將stub代理綁定到Registry服務的URL上 */
            java.rmi.Naming.rebind("rmi://localhost:1099/hello", hello);
            System.out.print("Ready");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. 客戶端

  1. /* Hello_RMI_Client.java */  
  2. package mytest;  
  3. import java.rmi.Naming;  
  4.   
  5. /* 客戶端向服務端請求遠程對象服務 */  
  6. public class Hello_RMI_Client {  
  7.     public static void main(String[] args) {  
  8.         try {  
  9.             /* 從RMI Registry中請求stub 
  10.              * 如果RMI Service就在本地機器上,URL就是:rmi://localhost:1099/hello 
  11.              * 否則,URL就是:rmi://RMIService_IP:1099/hello 
  12.              */  
  13.             IHello hello = (IHello) Naming.lookup(”rmi://localhost:1099/hello”);  
  14.             /* 通過stub調用遠程接口實現 */  
  15.             System.out.println(hello.sayHello(”zhangxianxin”));  
  16.         } catch (Exception e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
  20. }  
/* Hello_RMI_Client.java */
package mytest;
import java.rmi.Naming;

/* 客戶端向服務端請求遠程對象服務 */
public class Hello_RMI_Client {
    public static void main(String[] args) {
        try {
            /* 從RMI Registry中請求stub
             * 如果RMI Service就在本地機器上,URL就是:rmi://localhost:1099/hello
             * 否則,URL就是:rmi://RMIService_IP:1099/hello
             */
            IHello hello = (IHello) Naming.lookup("rmi://localhost:1099/hello");
            /* 通過stub調用遠程接口實現 */
            System.out.println(hello.sayHello("zhangxianxin"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

RMI 應用各個類的交互時序圖


RMI應用需要用到的類






1. RemoteObject抽象類實現了Remote接口和序列化Serializable接口,它和它的子類提供RMI服務器函數。
爲什麼遠端調用方法拋出的RemoteException異常的父類竟然是IOException呢?
這是因爲,遠端調用方法其實際上是通過網絡IO進行的。

2. 當客戶端通過RMI註冊表找到一個遠程接口的時候,所得到的其實是遠程接口的一個動態代理對象。當客戶端調用其中的方法的時候,方法的參數對象會在序列化之後,傳輸到服務器端。服務器端接收到之後,進行反序列化得到參數對象。並使用這些參數對象,在服務器端調用實際的方法。調用的返回值Java對象經過序列化之後,再發送回客戶端。客戶端再經過反序列化之後得到Java對象,返回給調用者。這中間的序列化過程對於使用者來說是透明的,由動態代理對象自動完成。除了序列化之外,RMI還使用了動態類加載技術。當需要進行反序列化的時候,如果該對象的類定義在當前JVM中沒有找到,RMI會嘗試從遠端下載所需的類文件定義。可以在RMI程序啓動的時候,通過JVM參數java.rmi.server.codebase來指定動態下載Java類文件的URL。

實現一個stub和skeleton程序

明白了RMI應用的原理後,可以自行實現一個stub和skeleton程序,進一步探索RMI的代理訪問原理。
RMI的本質就是實現在不同JVM之間的調用,它的實現方法就是在兩個JVM中各開一個Stub和Skeleton,二者通過socket通信來實現參數和返回值的傳遞。

1. 定義一個Person的接口,其中有兩個business method, getAge() 和getName()
Person代碼:
  1. public interface Person {        
  2.     public int getAge() throws Throwable;        
  3.     public String getName() throws Throwable;        
  4. }  
public interface Person {      
    public int getAge() throws Throwable;      
    public String getName() throws Throwable;      
}

2. Stub的實現 
Person_Stub代碼:   
  1. import java.io.ObjectOutputStream;        
  2. import java.io.ObjectInputStream;        
  3. import java.net.Socket;        
  4. public class Person_Stub implements Person {        
  5.     private Socket socket;        
  6.     public Person_Stub() throws Throwable {        
  7.         // connect to skeleton        
  8.         socket = new Socket(“computer_name”9000);        
  9.     }        
  10.     public int getAge() throws Throwable {        
  11.         // pass method name to skeleton        
  12.         ObjectOutputStream outStream =        
  13.             new ObjectOutputStream(socket.getOutputStream());        
  14.         outStream.writeObject(”age”);        
  15.         outStream.flush();        
  16.         ObjectInputStream inStream =        
  17.             new ObjectInputStream(socket.getInputStream());        
  18.         return inStream.readInt();        
  19.     }        
  20.     public String getName() throws Throwable {        
  21.         // pass method name to skeleton        
  22.         ObjectOutputStream outStream =        
  23.             new ObjectOutputStream(socket.getOutputStream());        
  24.         outStream.writeObject(”name”);        
  25.         outStream.flush();        
  26.         ObjectInputStream inStream =        
  27.             new ObjectInputStream(socket.getInputStream());        
  28.         return (String)inStream.readObject();        
  29.     }  
  30. }   
import java.io.ObjectOutputStream;      
import java.io.ObjectInputStream;      
import java.net.Socket;      
public class Person_Stub implements Person {      
    private Socket socket;      
    public Person_Stub() throws Throwable {      
        // connect to skeleton      
        socket = new Socket("computer_name", 9000);      
    }      
    public int getAge() throws Throwable {      
        // pass method name to skeleton      
        ObjectOutputStream outStream =      
            new ObjectOutputStream(socket.getOutputStream());      
        outStream.writeObject("age");      
        outStream.flush();      
        ObjectInputStream inStream =      
            new ObjectInputStream(socket.getInputStream());      
        return inStream.readInt();      
    }      
    public String getName() throws Throwable {      
        // pass method name to skeleton      
        ObjectOutputStream outStream =      
            new ObjectOutputStream(socket.getOutputStream());      
        outStream.writeObject("name");      
        outStream.flush();      
        ObjectInputStream inStream =      
            new ObjectInputStream(socket.getInputStream());      
        return (String)inStream.readObject();      
    }
} 
Person_Stub是建立socket連接,並向Skeleton發請求,然後通過Skeleton調用PersonServer的方法,最後接收返回的結果。    

3. 骨架(Skeleton)的實現
Person_Skeleton代碼:
  1. import java.io.ObjectOutputStream;        
  2. import java.io.ObjectInputStream;        
  3. import java.net.Socket;        
  4. import java.net.ServerSocket;        
  5. public class Person_Skeleton extends Thread {        
  6.     private PersonServer myServer;        
  7.     public Person_Skeleton(PersonServer server) {        
  8.         // get reference of object server        
  9.         this.myServer = server;        
  10.     }        
  11.     public void run() {        
  12.         try {        
  13.             // new socket at port 9000        
  14.             ServerSocket serverSocket = new ServerSocket(9000);        
  15.             // accept stub’s request        
  16.             Socket socket = serverSocket.accept();        
  17.             while (socket != null) {        
  18.                 // get stub’s request        
  19.                 ObjectInputStream inStream =        
  20.                     new ObjectInputStream(socket.getInputStream());        
  21.                 String method = (String)inStream.readObject();        
  22.                 // check method name        
  23.                 if (method.equals(“age”)) {        
  24.                     // execute object server’s business method        
  25.                     int age = myServer.getAge();        
  26.                     ObjectOutputStream outStream =        
  27.                         new ObjectOutputStream(socket.getOutputStream());        
  28.                     // return result to stub        
  29.                     outStream.writeInt(age);        
  30.                     outStream.flush();        
  31.                 }        
  32.                 if(method.equals(“name”)) {        
  33.                     // execute object server’s business method        
  34.                     String name = myServer.getName();        
  35.                     ObjectOutputStream outStream =        
  36.                         new ObjectOutputStream(socket.getOutputStream());        
  37.                     // return result to stub        
  38.                     outStream.writeObject(name);        
  39.                     outStream.flush();        
  40.                 }        
  41.             }        
  42.         } catch(Throwable t) {        
  43.             t.printStackTrace();        
  44.             System.exit(0);        
  45.         }        
  46.     }             
  47. }  
import java.io.ObjectOutputStream;      
import java.io.ObjectInputStream;      
import java.net.Socket;      
import java.net.ServerSocket;      
public class Person_Skeleton extends Thread {      
    private PersonServer myServer;      
    public Person_Skeleton(PersonServer server) {      
        // get reference of object server      
        this.myServer = server;      
    }      
    public void run() {      
        try {      
            // new socket at port 9000      
            ServerSocket serverSocket = new ServerSocket(9000);      
            // accept stub's request      
            Socket socket = serverSocket.accept();      
            while (socket != null) {      
                // get stub's request      
                ObjectInputStream inStream =      
                    new ObjectInputStream(socket.getInputStream());      
                String method = (String)inStream.readObject();      
                // check method name      
                if (method.equals("age")) {      
                    // execute object server's business method      
                    int age = myServer.getAge();      
                    ObjectOutputStream outStream =      
                        new ObjectOutputStream(socket.getOutputStream());      
                    // return result to stub      
                    outStream.writeInt(age);      
                    outStream.flush();      
                }      
                if(method.equals("name")) {      
                    // execute object server's business method      
                    String name = myServer.getName();      
                    ObjectOutputStream outStream =      
                        new ObjectOutputStream(socket.getOutputStream());      
                    // return result to stub      
                    outStream.writeObject(name);      
                    outStream.flush();      
                }      
            }      
        } catch(Throwable t) {      
            t.printStackTrace();      
            System.exit(0);      
        }      
    }           
}

Skeleton類 extends from Thread,它長駐在後臺運行,隨時接收client發過來的request。並根據發送過來的key去調用相應的business method。

4. Person的實現PersonServer類
PersonServer代碼:

  1. public class PersonServer implements Person {        
  2.     private int age;        
  3.     private String name;        
  4.     public PersonServer(String name, int age) {        
  5.         this.age = age;        
  6.         this.name = name;        
  7.     }        
  8.     public int getAge() {        
  9.         return age;        
  10.     }        
  11.     public String getName() {        
  12.         return name;        
  13.     }        
  14.       
  15.     public static void main(String args []) {        
  16.         // new object server        
  17.         PersonServer person = new PersonServer(“Richard”34);        
  18.         Person_Skeleton skel = new Person_Skeleton(person);        
  19.         skel.start();        
  20.     }   
  21. }  
public class PersonServer implements Person {      
    private int age;      
    private String name;      
    public PersonServer(String name, int age) {      
        this.age = age;      
        this.name = name;      
    }      
    public int getAge() {      
        return age;      
    }      
    public String getName() {      
        return name;      
    }      

    public static void main(String args []) {      
        // new object server      
        PersonServer person = new PersonServer("Richard", 34);      
        Person_Skeleton skel = new Person_Skeleton(person);      
        skel.start();      
    } 
}
PersonServer中對Person的接口進行了真正的實現,創建PersonServer實例對象,並啓動Person_Skeleton服務。

5. Client的實現
PersonClient 代碼:
  1. public class PersonClient {        
  2.     public static void main(String [] args) {        
  3.         try {        
  4.             Person person = new Person_Stub();        
  5.             int age = person.getAge();        
  6.             String name = person.getName();        
  7.             System.out.println(name + ” is ” + age + “ years old”);        
  8.         } catch(Throwable t) {        
  9.             t.printStackTrace();        
  10.         }        
  11.     }        
  12. }        
public class PersonClient {      
    public static void main(String [] args) {      
        try {      
            Person person = new Person_Stub();      
            int age = person.getAge();      
            String name = person.getName();      
            System.out.println(name + " is " + age + " years old");      
        } catch(Throwable t) {      
            t.printStackTrace();      
        }      
    }      
}      

Client(PersonClient)的本質是,它要知道Person接口的定義,並實例一個Person_Stub,通過Stub來調用business method,至於Stub怎麼去和Server溝通,Client就不用管了。
注意它的寫法:
Person person = new Person_Stub();而不是Person_Stub person = new Person_Stub();爲什麼?因爲要面向接口編程嘛,呵呵。
實際上,對於PersonClient來說,它只關注Person接口,而且它的本質就是遠程調用Person接口,而Person_Stub只是負責代理PersonClient去與Server交互。

參考

1. http://www.blogjava.NET/zhenyu33154/articles/320245.html

2. http://guojuanjun.blog.51cto.com/277646/1423392/

3. http://blog.sina.com.cn/s/blog_4918a7d90100oftg.html

4. http://haolloyin.blog.51cto.com/1177454/332426/

5. http://www.it165.net/pro/html/201404/11411.html

6. http://spidermanzy.iteye.com/blog/1741045

7. http://www.infoq.com/cn/articles/cf-java-object-serialization-rmi/

8. http://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html


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