Java的RMI遠程方法調用實現和應用
最近在學習Dubbo,RMI是很重要的底層機制,RMI(Remote Method Invocation)遠程方法調用是一種計算機之間利用遠程對象互相調用實現雙方通訊的一種通訊機制。使用這種機制,某一臺計算機(即JVM虛擬機)上的對象可以調用另外一臺計算機上的對象來獲取遠程數據。
RMI的實現對建立分布式Java應用程序至關重要,是Java體系非常重要的底層技術。
1.RMI的概念和原理
RMI思路是在客戶端安裝一個代理(proxy),代理是位於客戶端虛擬機中的一個對象,對於客戶端對象,看起來就像訪問的遠程對象一樣,客戶端代理會使用網絡協議與服務器進行通信。
同樣的服務端也會有一個代理對象來進行通信的繁瑣工作。
在RMI中,客戶端的代理對象被稱為存根(Stub),存根位於客戶端機器上,它知道如何通過網絡與服務器聯系。存根會將遠程方法所需的參數打包成一組字節。對參數編碼的過程被稱為參數編組(parameter marshalling),參數編組的目的是將參數轉換成適合在虛擬機之間進行傳遞的形式。在RMI協議中,對象時使用序列化機制進行編碼的。
總的來說,客戶端的存根方法構造了一個信息塊,它由以下幾部分組成
被使用的遠程對象的標識符;
被調用的方法的描述;
編組後的參數;
然後,存根將此信息發送給服務器。在服務器的一端,接收器對象執行以下動作:
定位要調用的遠程對象;
調用所需的方法,並傳遞客戶端提供的參數;
將返回值編組,打包送回給客戶端存根;
客戶端存根對來自服務器端的返回值或異常進行反編組,其結果就成為了調用存根返回值。
2.RMI體系結構
樁/框架(Stub/Skeleton)層:客戶端的樁和服務器端的框架;
遠程引用(remote reference)層:處理遠程引用行為
傳送層(transport):連接的建立和管理,以及遠程對象的跟蹤
3.RMI與代理模式
RMI的實現是典型的代理模式思想。
(1)代理模式的UML圖
代理模式為其他對象提供一種代理以控制對這個對象的訪問,把調用者與被調用者分離開,由代理負責傳遞信息來完成調用。
比如你想買美版Iphone6s,朋友出國,幫你海淘帶回,整個過程就是代理模式,朋友作為代理,代你完成你想進行的操作。
代理模式能將代理對象與真正被調用的對象分離,在一定程度上降低了系統的耦合度;
在客戶端和目標對象之間起到一個中介作用,這樣可以起到保護目標對象的作用,代理對象也可以對目標對象調用之前進行其他操作。
代理模式在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢;同時也增加了系統的復雜度。
(2)代理模式在RMI這種的體現
遠程代理的內部機制是這樣的:
下面的RMI實例,其實就是一個代理模式的應用。
4.RMI的簡單實例
RMI的開發步驟如下:
先創建遠程接口及聲明遠程方法,註意這是實現雙方通訊的接口,需要繼承Remote
開發一個類來實現遠程接口及遠程方法,值得註意的是實現類需要繼承UnicastRemoteObject
通過javac命令編譯文件,通過java -server 命令註冊服務,啟動遠程對象
最後客戶端查找遠程對象,並調用遠程方法
(1)服務端遠程接口
創建遠程接口SearchService,接口必須繼承Remote類,每一個定義方法都要拋出RemoteException異常
import java.rmi.Remote; import java.rmi.RemoteException; public interface SearchService extends Remote { public User findUser(String id) throws RemoteException;; }
(2)建立SearchServiceImpl實現遠程接口,註意此為遠程對象實現類,需要繼承UnicastRemoteObject
import java.rmi.RemoteException; public class SearchServiceImpl implements SearchService{ /** * 拋出RemoteException * @throws RemoteException */ public SearchServiceImpl() throws RemoteException { super(); } @Override public User findUser(String id) throws RemoteException { /** * 模擬查找返回數據 */ User user=new User(id,"Tom","18歲"); return user; } }
(3)為服務建立一個Model層,此對象需要遠程傳輸,所以必須實現implements Serializable序列化接口,也就是可以在client和server端進行傳輸的可序列化對象
新建User類,用於數據傳輸:
public class User implements Serializable{ private static final long serialVersionUID = 1L; private String id; private String name; private String age; public User(String id,String name,String age){ this.id=id; this.name=name; this.age=age; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String toString(){ StringBuffer sb=new StringBuffer(); sb.append("~用戶id-"+id); sb.append("~用戶姓名-"+id); sb.append("~用戶年齡-"+id); return sb.toString(); } }
(4)建立服務器端,在服務器端註冊RMI通訊端口與通訊路徑
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; /** * @author BingYue */ public class Process { public static void main(String[] args){ try { SearchService searchService=new SearchServiceImpl(); //註冊通信端口 Registry registry=LocateRegistry.createRegistry(5678); //註冊通訊路徑 Naming.rebind("rmi://127.0.0.1:5678/searchService", searchService); System.out.println("Service Start!"); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
(5)創建客戶端,導入上面的實體類和接口,通過Naming.lookup()的方式調用
如果使用IDE,就可以新建一個項目,Client代碼如下:
import java.rmi.Naming; /** * @author BingYue */ public class Client { public static void main(String[] args){ try { //調用遠程對象,註意RMI路徑與接口必須與服務器配置一致 SearchService searchService=(SearchService)Naming.lookup("rmi://127.0.0.1:5678/searchService"); User user=searchService.findUser("100"); System.out.println(user.toString()); } catch (Exception e) { e.printStackTrace(); } } }
(6)分別運行服務端和客戶端,獲得遠程調用結果
(1)Remote接口:是一個不定義方法的標記接口
Public interface Remote{
}
Java的RMI遠程方法調用實現和應用