最近在學習dubbo,rmi是很重要的底層機制,rmi(remote method invocation)遠端方法調用是一種計算機之間利用遠端對象互相調用實作雙方通訊的一種通訊機制。使用這種機制,某一台計算機(即jvm虛拟機)上的對象可以調用另外一台計算機上的對象來擷取遠端資料。
rmi的實作對建立分布式java應用程式至關重要,是java體系非常重要的底層技術。
rmi思路是在用戶端安裝一個代理(proxy),代理是位于用戶端虛拟機中的一個對象,對于用戶端對象,看起來就像通路的遠端對象一樣,用戶端代理會使用網絡協定與伺服器進行通信。
同樣的服務端也會有一個代理對象來進行通信的繁瑣工作。

在rmi中,用戶端的代理對象被稱為存根(stub),存根位于用戶端機器上,它知道如何通過網絡與伺服器聯系。存根會将遠端方法所需的參數打包成一組位元組。對參數編碼的過程被稱為參數編組(parameter marshalling),參數編組的目的是将參數轉換成适合在虛拟機之間進行傳遞的形式。在rmi協定中,對象時使用序列化機制進行編碼的。
總的來說,用戶端的存根方法構造了一個資訊塊,它由以下幾部分組成
被使用的遠端對象的辨別符;
被調用的方法的描述;
編組後的參數;
然後,存根将此資訊發送給伺服器。在伺服器的一端,接收器對象執行以下動作:
定位要調用的遠端對象;
調用所需的方法,并傳遞用戶端提供的參數;
捕獲傳回值或調用産生的異常;
将傳回值編組,打包送回給用戶端存根;
用戶端存根對來自伺服器端的傳回值或異常進行反編組,其結果就成為了調用存根傳回值。
樁/架構(stub/skeleton)層:用戶端的樁和伺服器端的架構;
遠端引用(remote reference)層:處理遠端引用行為
傳送層(transport):連接配接的建立和管理,以及遠端對象的跟蹤
rmi的實作是典型的代理模式思想。
(1)代理模式的uml圖
代理模式為其他對象提供一種代理以控制對這個對象的通路,把調用者與被調用者分離開,由代理負責傳遞資訊來完成調用。
比如你想買美版iphone6s,朋友出國,幫你海淘帶回,整個過程就是代理模式,朋友作為代理,代你完成你想進行的操作。
代理模式能将代理對象與真正被調用的對象分離,在一定程度上降低了系統的耦合度;
在用戶端和目标對象之間起到一個中介作用,這樣可以起到保護目标對象的作用,代理對象也可以對目标對象調用之前進行其他操作。
代理模式在用戶端和目标對象增加一個代理對象,會造成請求處理速度變慢;同時也增加了系統的複雜度。
(2)代理模式在rmi這種的展現
遠端代理的内部機制是這樣的:
下面的rmi執行個體,其實就是一個代理模式的應用。
rmi的開發步驟如下:
先建立遠端接口及聲明遠端方法,注意這是實作雙方通訊的接口,需要繼承remote
開發一個類來實作遠端接口及遠端方法,值得注意的是實作類需要繼承unicastremoteobject
通過javac指令編譯檔案,通過java -server 指令注冊服務,啟動遠端對象
最後用戶端查找遠端對象,并調用遠端方法
(1)服務端遠端接口
建立遠端接口searchservice,接口必須繼承remote類,每一個定義方法都要抛出remoteexception異常
1
2
3
4
5
6
<code>import</code> <code>java.rmi.remote;</code>
<code>import</code> <code>java.rmi.remoteexception;</code>
<code>public</code> <code>interface</code> <code>searchservice </code><code>extends</code> <code>remote {</code>
<code> </code><code>public</code> <code>user finduser(string id) </code><code>throws</code> <code>remoteexception;;</code>
<code>}</code>
(2)建立searchserviceimpl實作遠端接口,注意此為遠端對象實作類,需要繼承unicastremoteobject
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<code>public</code> <code>class</code> <code>searchserviceimpl </code><code>implements</code> <code>searchservice{</code>
<code> </code><code>/**</code>
<code> </code><code>* 抛出remoteexception</code>
<code> </code><code>* @throws remoteexception</code>
<code> </code><code>*/</code>
<code> </code><code>public</code> <code>searchserviceimpl() </code><code>throws</code> <code>remoteexception {</code>
<code> </code><code>super</code><code>();</code>
<code> </code><code>}</code>
<code> </code>
<code> </code><code>@override</code>
<code> </code><code>public</code> <code>user finduser(string id) </code><code>throws</code> <code>remoteexception {</code>
<code> </code><code>/**</code>
<code> </code><code>* 模拟查找傳回資料</code>
<code> </code><code>*/</code>
<code> </code><code>user user=</code><code>new</code> <code>user(id,</code><code>"tom"</code><code>,</code><code>"18歲"</code><code>);</code>
<code> </code><code>return</code> <code>user;</code>
<code> </code><code>}</code>
(3)為服務建立一個model層,此對象需要遠端傳輸,是以必須實作implements serializable序列化接口,也就是可以在client和server端進行傳輸的可序列化對象
建立user類,用于資料傳輸:
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<code>public</code> <code>class</code> <code>user </code><code>implements</code> <code>serializable{</code>
<code> </code><code>private</code> <code>static</code> <code>final</code> <code>long</code> <code>serialversionuid = 1l;</code>
<code> </code><code>private</code> <code>string id;</code>
<code> </code><code>private</code> <code>string name;</code>
<code> </code><code>private</code> <code>string age;</code>
<code> </code><code>public</code> <code>user(string id,string name,string age){</code>
<code> </code><code>this</code><code>.id=id;</code>
<code> </code><code>this</code><code>.name=name;</code>
<code> </code><code>this</code><code>.age=age;</code>
<code> </code><code>public</code> <code>string getid() {</code>
<code> </code><code>return</code> <code>id;</code>
<code> </code><code>public</code> <code>void</code> <code>setid(string id) {</code>
<code> </code><code>this</code><code>.id = id;</code>
<code> </code><code>public</code> <code>string getname() {</code>
<code> </code><code>return</code> <code>name;</code>
<code> </code><code>public</code> <code>void</code> <code>setname(string name) {</code>
<code> </code><code>this</code><code>.name = name;</code>
<code> </code><code>public</code> <code>string getage() {</code>
<code> </code><code>return</code> <code>age;</code>
<code> </code><code>public</code> <code>void</code> <code>setage(string age) {</code>
<code> </code><code>this</code><code>.age = age;</code>
<code> </code><code>public</code> <code>string tostring(){</code>
<code> </code><code>stringbuffer sb=</code><code>new</code> <code>stringbuffer();</code>
<code> </code><code>sb.append(</code><code>"~使用者id-"</code><code>+id);</code>
<code> </code><code>sb.append(</code><code>"~使用者姓名-"</code><code>+id);</code>
<code> </code><code>sb.append(</code><code>"~使用者年齡-"</code><code>+id);</code>
<code> </code><code>return</code> <code>sb.tostring();</code>
(4)建立伺服器端,在伺服器端注冊rmi通訊端口與通訊路徑
<code>import</code> <code>java.net.malformedurlexception;</code>
<code>import</code> <code>java.rmi.naming;</code>
<code>import</code> <code>java.rmi.registry.locateregistry;</code>
<code>import</code> <code>java.rmi.registry.registry;</code>
<code>/**</code>
<code> </code><code>* @author bingyue</code>
<code> </code><code>*/</code>
<code>public</code> <code>class</code> <code>process {</code>
<code> </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args){</code>
<code> </code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>searchservice searchservice=</code><code>new</code> <code>searchserviceimpl();</code>
<code> </code><code>//注冊通信端口</code>
<code> </code><code>registry registry=locateregistry.createregistry(</code><code>5678</code><code>);</code>
<code> </code><code>//注冊通訊路徑</code>
<code> </code><code>system.out.println(</code><code>"service start!"</code><code>);</code>
<code> </code>
<code> </code><code>} </code><code>catch</code> <code>(remoteexception e) {</code>
<code> </code><code>e.printstacktrace();</code>
<code> </code><code>} </code><code>catch</code> <code>(malformedurlexception e) {</code>
(5)建立用戶端,導入上面的實體類和接口,通過naming.lookup()的方式調用
如果使用ide,就可以建立一個項目,client代碼如下:
<code>public</code> <code>class</code> <code>client {</code>
<code> </code><code>//調用遠端對象,注意rmi路徑與接口必須與伺服器配置一緻</code>
<code> </code><code>user user=searchservice.finduser(</code><code>"100"</code><code>);</code>
<code> </code><code>system.out.println(user.tostring());</code>
<code> </code><code>} </code><code>catch</code> <code>(exception e) {</code>
(6)分别運作服務端和用戶端,獲得遠端調用結果
(1)remote接口:是一個不定義方法的标記接口
<code>public </code><code>interface</code> <code>remote{}</code>
在rmi中,遠端接口聲明了可以從遠端java虛拟機中調用的方法集。遠端接口滿足下列要求:
遠端接口必須直接或間接擴充java.rmi.remote接口,且必須聲明為public,除非用戶端于遠端接口在同一包中;
在遠端接口中的方法在聲明時,除了要抛出與應用程式有關的一場之外,還必須包括remoteexception(或它的超類,ioexcepion或exception)異常;
在遠端方法聲明中,作為參數或傳回值聲明的遠端對象必須聲明為遠端接口,而非該接口的實作類。
(2)remoteobject抽象類實作了remote接口和序列化serializable接口,它和它的子類提供rmi伺服器函數。
(3)locateregistry final()類用于獲得特定主機的引導遠端對象注冊伺服器程式的引用(即建立stub),或者建立能在特定端口接收調用的遠端對象注冊服務程式。
伺服器端:向其他客戶機提供遠端對象服務
<code>someservice servcie=……;</code><code>//遠端對象服務</code>
<code>registry registry=locateregisty.getregistry();</code><code>//registry是個接口,他繼承了remote,此方法傳回本地主機在預設系統資料庫端口 1099 上對遠端對象 registry 的引用。</code>
<code>getregistry(</code><code>int</code> <code>port) 傳回本地主機在指定 port 上對遠端對象 registry 的引用;</code>
<code>getregistry(string host) 傳回指定 host 在預設系統資料庫端口 </code><code>1099</code> <code>上對遠端對象 registry 的引用;</code>
<code>getregistry(string host, </code><code>int</code> <code>port) 傳回指定的 host 和 port 上對遠端對象 registry 的引用</code>
<code>registry.bind(“i serve”,service);</code><code>// bind(string name,remote obj) 綁定對此系統資料庫中指定 name 的遠端引用。name : 與該遠端引用相關的名稱 obj : 對遠端對象(通常是一個 stub)的引用</code>
<code>unbind(string name)移除系統資料庫中指定name的綁定。</code>
<code>rebind(string name,remote obj)重新綁定,如果name已存在,但是remote不一樣則替換,如果remote一樣則丢棄現有的綁定</code>
<code>lookup(string name) 傳回系統資料庫中綁定到指定 name 的遠端引用,傳回remote</code>
<code>string[] list() 傳回在此系統資料庫中綁定的名稱的數組。該數組将包含一個此系統資料庫中調用此方法時綁定的名稱快照。</code>
客戶機端:向伺服器提供相應的服務請求
<code>registry registry=locateregisty.getregistry();</code>
<code>someservice servcie=(someservice)registry.lookup(“i serve”);</code>
<code>servcie.requestservice();</code>
(4) naming類和registry類類似
用戶端:
<code>naming.lookup(string url)</code>
伺服器端:
<code>registry registry=locateregistry.createregistry(</code><code>int</code> <code>port);</code>
<code>naming.rebind(“service”,service);</code>
(5) rmisecuritymanager類
在rmi引用程式中,如果沒有設定安全管理器,則隻能從本地類路徑加載stub和類,這可以確定應用程式不受由遠端方法調用所下載下傳的代碼侵害
在從遠端主機下載下傳代碼之前必須執行以下代碼來安裝rmisecuritymanager:
<code>system.setsecuritymanager(</code><code>new</code> <code>rmisecuritymanager());</code>
rmi的用途是為分布式java應用程式之間的遠端通信提供服務,提供分布式服務。
目前主要應用時封裝在各個j2ee項目架構中,例如spring封裝了rmi技術。
(1)在spring中實作rmi
在伺服器端定義服務的接口,定義特定的類實作這些接口;
在伺服器端使用org.springframework.remoting.rmi.rmiserviceexporter類來注冊服務;
在用戶端使用org.springframework.remoting.rmi.rmiproxyfactorybean來實作遠端服務的代理功能;
在用戶端定義通路與伺服器端服務接口相同的類
(2)在dubbo中的應用
dubbo支援不同的協定,其中rmi協定底層就是java rmi的實作,
rmi協定的invoker轉為exporter發生在rmiprotocol類的export方法,
通過spring或dubbo或jdk來實作rmi服務,通訊細節由jdk底層來實作,節省了不少工作量。