天天看點

Java RMI(遠端方法調用) 執行個體與分析 (轉)

目的:

  通過本文,可以加深對Java RMI的了解,知道它的工作原理,怎麼使用等. 也為了加深我自己的了解,故整理成文.不足之處,還望指出.

概念解釋:

RMI(RemoteMethodInvocation):遠端方法調用,顧名思義,通過遠端的方式調用非本地對象的方法并傳回結果。使用遠端調用通常解決本地計算瓶頸問題,例如分布式記算,最近很火的阿爾法狗人機大戰,據說運算使用上千個CPU。

JRMP(java remote method protocol):java遠端方法協定,這是完成java到java遠端調用的協定,基于TCP協定。

stub與skeleton:這兩個概念下面會用到,這裡解釋下,skeleton是放在服務端的代理,它知道真正的對象在哪。stub是放在用戶端的代理,它記錄了查找和調用skeleton資訊。了解成遠端對象引用也成.

容易混淆的概念:

  遠端方法調用與遠端過程調用的差別:遠端方法調用是java獨有的,基于JRMP對象流協定實作,支援傳輸java序列化對象。遠端過程調用是基于socket技術實作的,不能傳輸java對象,socket套接字協定支援多種語言。它們都是基于TCP協定傳輸。遠端方法調用傳輸的是java序列化對象和基本資料類型,而遠端過程調用不支援傳輸對象。

RMI調用模型:

Java RMI(遠端方法調用) 執行個體與分析 (轉)

從宏觀看,想要遠端調用需要做兩件事情,1,服務端向本地對象系統資料庫中注冊能被調用的遠端對象. 2,用戶端向遠端對象系統資料庫請求遠端對象的引用.

Java中RMI實作:

先通過一個例子了解Java中RMI是怎麼用的,然後再根據代碼分析源碼是如何實作的.

1,先建立遠端對象接口,繼承自Remote(稍後源碼中有分析為什麼要有這個接口)

package remote.test;

import java.rmi.Remote;
import java.rmi.RemoteException;
/**
 * 遠端接口,實作Remote
 * @author lxz
 *
 */
public interface IRemote extends Remote{

    public String show()throws RemoteException;//聲明方法
}      

2,接口實作,需要繼承UnicastRemoteObject類,等會分析

package remote.test;

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

/**
 * 遠端接口實作,繼承UnicastRemoteObject
 * @author lxz
 *
 */
public class RemoteImpl extends UnicastRemoteObject  implements IRemote{
    public RemoteImpl()throws RemoteException{}//構造方法
    public String show()throws RemoteException{//調用方法實作
        System.out.println("進入");
        System.out.println(this.toString());
        return "遠端調用成功";
    }
}      

3,服務端向本地端口1234對象系統資料庫注冊對象和它的名字

package remote.test;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/**
 *  服務端啟動,建立端口上的對象注冊清單,向對象系統資料庫中注冊遠端調用對象
 * @author lxz
 *
 */
public class TestServer {
    public static void main(String[] args) throws MalformedURLException, RemoteException, AlreadyBoundException, InterruptedException {
        RemoteImpl r = new RemoteImpl();//建立遠端對象
        Registry rr = LocateRegistry.createRegistry(1234); //建立1234端口上的對象系統資料庫,如果已經建立了就用getRegistry方法擷取
        rr.bind("testrmi", r);//向系統資料庫中注冊對象
        System.out.println(r.toString());
    }
}      

4,根據JDK API,以上遠端服務就算搭建完畢了,下面通過用戶端調用測試

package remote.test;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

/**
 * 用戶端啟動,獲得遠端的對象系統資料庫中的對象引用
 * @author lxz
 *
 */
public class TestClient {

    public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
        IRemote r = (IRemote) Naming.lookup("rmi://localhost:1234/testrmi");//擷取遠端1234端口對象系統資料庫中testrmi的stub
        String a = r.show();//調用引用的方法,實際上調用的是stub,由stub與服務端互動并傳回結果
        System.out.println(a);
    }
}      

執行結果如下:

------------------------------------------

服務端:

RemoteImpl[UnicastServerRef [liveRef: [endpoint:[192.168.1.253:58169](local),objID:[-60651394:1539d5944e6:-7fff, -6910034932968554489]]]]

用戶端:

遠端調用成功

------------------------------------------

這樣就完成了一個遠端對象注冊與遠端對象方法調用的完整例子. 現在根據這個例子來分析它為什麼要繼承UnicastRemoteObject,實作Remote,向系統資料庫注冊等等.

首先遠端對象實作類中需要繼承UnicastRemoteObject類,UnicastRemoteObject具有注冊為遠端對象,生成遠端引用的功能等,所有都已經被JDK封裝好了,不需要編寫,其中的實作有些是sun包開頭的,不公開.

UnicastRemoteObject繼承關系:

Java RMI(遠端方法調用) 執行個體與分析 (轉)

有了遠端對象實作類,看服務端的啟動邏輯,其中:

Registry rr = LocateRegistry.createRegistry(1234);      

LocateRegistry類:用于建立或擷取某端口的對象系統資料庫

LocateRegistry.createRegistry:這個方法表示獲得遠端對象系統資料庫引用,傳回Registry對象

Registry:真正操作遠端對象系統資料庫的接口

接着,

rr.bind("testrmi", r);      

利用Registry的對象,把剛剛建立的遠端對象注冊為名稱testrmi. 這裡還有一種寫法,效果是一樣的.

LocateRegistry.createRegistry(1234); //建立,如果已經建立了就可省略這一句
Naming.bind("rmi://localhost:1234/testrmi", r);//需要帶上端口      

Naming:與對象系統資料庫互動的工具類

上面是服務端從遠端對象建立到對象注冊的整個邏輯.用戶端調用的邏輯比較簡單,先通過Naming工具類擷取到遠端對象的引用以後,就可以正常使用了

(IRemote) Naming.lookup("rmi://localhost:1234/testrmi");      

這裡傳回的"引用"和通常講的對象引用不同,是遠端對象的引用資訊.拿到這個"引用"以後就可以像使用真正的對象一樣調用其中的方法.