綜述
Rmi自從JDK1.1就已經出現了。而對于為什么在JAVA的世界里需要一個這樣 思想理念就需要看下:RMI問世由來。其實真正在國內使用到它的比較少,不過在前些年比較火的EJB就是在它的基礎上進一步深化的。從本質上來講RMI的興起正是為了設計分布式的客戶、服務器結構需求而應運而生的,而它的這種B/S結構思想能否和我們通常的JAVA編程更加貼切呢?言外之意就是能否讓這種分布式的狀態做到更加透明,作為開發人員只需要按照往常一樣開發JAVA應用程序一樣來開發分布式的結構。那現在的問題是如何來劃平這個鴻溝呢?首先我們來分析下在JAVA世界里它的一些特點因素:
l JAVA使用垃圾收集確定對象的生命周期。
l JAVA使用異常處理來報告運行期間的錯誤。這里就要和我們網絡通訊中的異常相聯系起來了。在B/S結構的網絡體系中我們的這種錯誤性是非常常見的。
l JAVA編寫的對象通過調用方法來調用。由于網絡通訊把我們的客戶與服務器之間阻隔開了。但是代理的一種方式可以很好的提供一種這樣的假象,讓開發人員或者使用者都感覺是在本地調用。
l JAVA允許一種高級的使用類加載器(CLassLoader)機制提供系統類路徑中沒有的類。這話什么意思?
主要特點
上面說到了分布式的方式和我們的JAVA中如何更好的劃平這個鴻溝,需要具備的特質。
那這里我們來看看我們所謂的RMI到底跟我們普通的JAVA(或者說JavaBean)存在一些什么樣的差異:
l RMI遠程異常(Remote Exception):在上面我們也提到了一個網絡通訊難免有一些無論是軟件級別的還是硬件級別的異常現象,有時候這些異常或許是一種無法預知的結果。讓我們開發人緣如何來回溯這種異常信息,這個是我們開發人員要關心的。因此在調用遠程對象的方法中我們必須在遠程接口中(接口是一種規范的標準行為)所以在調用的這個方法體上需要簽名注明:java.rmi,RemoteException.。這也就注明了此方法是需要調用遠程對象的。
l 值傳遞 :當把對象作為參數傳遞給一個普通的JAVA對象方法調用時,只是傳遞該對象的引用。請注意這里談到的是對象的“引用”一詞,如果在修改該參數的時候,是直接修改原始對象。它并不是所謂的一個對象的備份或者說拷貝(說白了就是在本JVM內存中的對象)。但是如果說使用的是RMI對象,則完全是拷貝的。這與普通對象有著鮮明的對比。也正是由于這種拷貝的資源消耗造就了下面要說到的性能缺失了。
l 調用開銷:凡是經過網絡通訊理論上來說都是一種資源的消耗。它需要通過編組與反編組方式不斷解析類對象。而且RMI本身也是一種需要返回值的一個過程定義。
l 安全性:一談到網絡通訊勢必會說到如何保證安全的進行。
概念定義
在開始進行原理梳理之前我們需要定義清楚幾個名詞。對于這些名詞的理解影響到后的深入進行。
1. Stub(存根,有些書上也翻譯成:樁基在EJB的相關書籍中尤為體現這個意思):
這里舉例說明這個概念起(或許不夠恰當)。例如大家因公出差后,都有存在一些報銷的發票或者說小票。對于你當前手頭所拿到的發票并不是一個唯一的,它同時還在你發生消費的地點有一個復印件,而這個復印件就是所謂的存根。但是這個存根上并沒有很多明細的描述,只是有一個大概的金額定義。它把很多的細節費用都忽略了。所以這個也是我們說的存根定義。而在我們RMI的存根定義就是使用了這樣一個理解:在與遠程發生通訊調用時,把通訊調用的所有細節都通過對象的封裝形式給隱藏在后端。這本身就符合OOAD的意思理念。而暴露出來的就是我們的接口方式,而這種接口方式又和服務器的對象具有相同的接口(這里就和我們前面舉例說的報銷單據聯系上了,報銷單據的存根不知道會有一個什么形式發生具體問題,而你手執的發票具體就需要到貴公司去報銷費用,而這里的公司財務處就是所謂的服務器端,它才是真正干實質性問題的。)因此作為開發人員只需要把精力集中在業務問題的解決上,而不需要考慮復雜的分布式計算。所有這些問題都交給RMI去一一處理。
2. Skeleton(一些書翻譯叫骨架,也叫結構體):它的內部就是真正封裝了一個類的形成調用體現機制。包括我們熟知的ServerSocket創建、接受、監聽、處理等。
3. Mashalling(編組):在內存中的對象轉換成字節流,以便能夠通過網絡連接傳輸。
4. Unmashalling(反編組):在內存中把字節流轉換成對象,以便本地化調用。
5. Serialization(序列化):編組中使用到的技術叫序列化。
6. Deserializationg(反序列化):反編組中使用到的技術叫反序列化。
客戶端
既然我們知道stub主要是以接口的方式來暴露體現,而stub主要 也是以代理的方式來具體實施。那在RMI中的這種接口有哪些特性呢?(Remote Interface)
1) 必須擴展(extends)java.rmi.Remote接口,因為遠程接口并不包含任何一個方法,而是作為一個標記出現,它就是需要告訴JVM在RunTime的時候哪些是常規對象,哪些屬于遠程對象。通過這種標識的定義能讓JVM了解類中哪些方法需要編組,通過了編組的方式才能通過網絡序列化的調用;
2) 接口必須為public(公共),它的好處不言而喻的——能夠方便的讓所有人員來調用。
3) 接口方法還需要以異常拋出(例如:RemoteException),至于它的用處我們在前面也提到這里就不再復述;
4) 在調用一個遠程對象期間(運行期間),方法的參數和返回值都要必須是可序列化的。至于為什么需要這么做?這里的緣由不用多說大家也應該清楚了解。
服務端
既然我們知道stub所做的事情是一個簡單的代理轉發動作,那我們真正要做的對象就在服務端來做了。對于使用簡單的RMI我們直接去指定,但是往往一旦使用了RMI對象就存在非常多的遠程方法調用,這個時候服務器端對于這么多的調用如何來判別或者說識別呢?這里就要說到的是對于RMI實現它會創建一個標識符,以便以后的stub可以調用轉發給服務器對象使用了,而這種方式我們通常叫服務器RMI的注冊機制。言外之意就是讓服務器端的對象注冊在RMI機制中,然后可以導出讓今后的stub按需來調用。那它又是如何做到這種方式的呢?對于RMI來說有兩種方式可以達到這種效果:
a) 直接使用UnicastRemoteObject的靜態方法:exportObject;
b) 繼承UnicastRemoteObject類則缺省的構造函數exportObject。
現在大家又會問他們之間又有什么區別呢?我該使用哪種方式來做呢,這不是很難做抉擇嗎?從一般應用場景來說區別并不是很大,但是,這里說了“但是”哦,呵呵。大家知道繼承的方式是把父類所具備的所有特質都可以完好無損的繼承到子類中而對于類的總老大:Object來說里面有:equals()、hashCode()、toString()等方法。這是個什么概念呢?意思就是說如果對于本地化的調用,他們兩個的方法(a,b)基本區別不是很大。但是我們這里強調的RMI如果是一種分布式的特定場景,具備使用哈希表這種特性就顯得尤為重要了。
剛才說了服務端采用什么方法行為導出對象的。那現在導出后的對象又對應會發生什么情況呢?
首先被導出的對象被分配一個標識符,這個標識符被保存為:java.rmi.server.ObjID對象中并被放到一個對象列表中或者一個映射中。而這里的ID是一個關鍵字,而遠程對象則是它的一個值(說到這大家有沒有覺得它原理非常像HashMap的特質呢?沒錯,其實就是使用了它的特性),這樣它就可以很好的和前面創建的stub溝通。如果調用了靜態方法UnicastRemoteObject.export(Remote …),RMI就會選擇任意一個端口號,但這只是第一調用發生在隨后的exportObject每次調用都把遠程對象導出到該遠程對象第一被導出時使用的端口號。這樣就不會產生混亂,會把先前一一導出的對象全部放入到列表中。當然如果采用的是指定端口的,則按照對應顯示的調用方式使用。這里稍作強調的是一個端口可以導出任意數目的對象。
(待續……)
posted on 2009-02-02 12:04
葉澍成 閱讀(3402)
評論(3) 編輯 收藏 所屬分類:
java基礎 、
分布式