目的:
通過本文,可以加深對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調用模型:
從宏觀看,想要遠端調用需要做兩件事情,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繼承關系:
有了遠端對象實作類,看服務端的啟動邏輯,其中:
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");
這裡傳回的"引用"和通常講的對象引用不同,是遠端對象的引用資訊.拿到這個"引用"以後就可以像使用真正的對象一樣調用其中的方法.