RMI 和 CORBA 常被視為相互競爭的技術(shù),因為兩者都提供對遠(yuǎn)程分布式對象的透明訪問。但這兩種技術(shù)實際上是相互補(bǔ)充的,一者的長處正好可以彌補(bǔ)另一者的短處。RMI 和 CORBA 的結(jié)合產(chǎn)生了 RMI-IIOP,RMI-IIOP 是企業(yè)服務(wù)器端 Java 開發(fā)的基礎(chǔ)。在本文中,Java 開發(fā)者 Damian Hagge 簡要介紹了 RMI-IIOP,然后為您展示如何構(gòu)建和運(yùn)行一個簡單的、基于 Java 的 RMI-IIOP 客戶機(jī)/服務(wù)器應(yīng)用程序。請親自看看 RMI 能在 IIOP 上工作得多么好。
1997 年,IBM 和 Sun Microsystems 啟動了一項旨在促進(jìn) Java 作為企業(yè)開發(fā)技術(shù)的發(fā)展的合作計劃。兩家公司特別著力于如何將 Java 用作服務(wù)器端語言,生成可以結(jié)合進(jìn)現(xiàn)有體系結(jié)構(gòu)的企業(yè)級代碼。所需要的就是一種遠(yuǎn)程傳輸技術(shù),它兼有 Java 的 RMI(Remote Method Invocation,遠(yuǎn)程方法調(diào)用)較少的資源占用量和更成熟的 CORBA(Common Object Request Broker Architecture,公共對象請求代理體系結(jié)構(gòu))技術(shù)的健壯性。出于這一需要,RMI-IIOP 問世了,它幫助將 Java 語言推向了目前服務(wù)器端企業(yè)開發(fā)的主流語言的領(lǐng)先地位。
在本文中,我將簡要介紹 RMI-IIOP,目標(biāo)是使您能開始在企業(yè)開發(fā)解決方案中使用這一技術(shù)。要解釋 RMI-IIOP 究竟是什么,我認(rèn)為提供一些關(guān)于 CORBA 和 RMI 的信息是重要的,這些信息您在各個技術(shù)的典型介紹中可能找不到。如果您對 CORBA 或 RMI 的基礎(chǔ)知識不熟悉,我建議您在往下讀之前先閱讀一些介紹性信息。請參閱參考資料,那里挑選了一些文章和教程。
在我具體討論 RMI-IIOP 之前,我們將先看一下 CORBA 和 RMI 用來對請求進(jìn)行數(shù)據(jù)編入的機(jī)制。CORBA 將是我們的主要示例,因為 RMI-IIOP 數(shù)據(jù)編入是建立在 CORBA 傳輸協(xié)議(IIOP)的基礎(chǔ)上的。我們將回顧一下該傳輸協(xié)議和 ORB(object request broker,對象請求代理)在網(wǎng)絡(luò)上發(fā)送請求、定位遠(yuǎn)程對象和傳輸對象方面的基本功能。
遠(yuǎn)程對象傳輸
對 CORBA 請求進(jìn)行數(shù)據(jù)編入是通過使用 IIOP 協(xié)議做到的。簡言之,IIOP 將以標(biāo)準(zhǔn)化格式構(gòu)造的任何 IDL(Interface Definition Language,接口定義語言)的元素表示為一系列字節(jié)。那就假設(shè)有一個 Java 客戶機(jī)正在將一個 CORBA 請求分派到 C++ 服務(wù)器吧??蛻魴C(jī)應(yīng)用程序以 Java 接口的形式擁有遠(yuǎn)程對象的引用,并調(diào)用該接口的一個操作。本質(zhì)上是,接口調(diào)用它對該操作的相應(yīng)實現(xiàn),這個實現(xiàn)將位于存根(stub)(存根是您將已經(jīng)用 idlj
從 IDL 生成了的)。
存根把方法調(diào)用分派到 ORB 中,ORB 由兩部分組成:客戶機(jī) ORB 和服務(wù)器 ORB??蛻魴C(jī) ORB 的職責(zé)是對請求進(jìn)行數(shù)據(jù)編入,放到網(wǎng)絡(luò)上,傳往特定位置。服務(wù)器 ORB 的職責(zé)是偵聽從網(wǎng)絡(luò)上傳下來的請求,并將這些請求轉(zhuǎn)換成語言實現(xiàn)能夠理解的方法調(diào)用。要了解對 CORBA ORB 的角色的更深入討論,請參閱參考資料部分。
存根分派了方法調(diào)用之后,客戶機(jī) ORB 將請求和所有參數(shù)轉(zhuǎn)換成標(biāo)準(zhǔn)化字節(jié)格式,在這種情況中是 IIOP。接著,請求通過導(dǎo)線被發(fā)送到服務(wù)器 ORB,服務(wù)器 ORB 應(yīng)該正在偵聽傳入請求。服務(wù)器端 ORB 將讀進(jìn)數(shù)據(jù)的字節(jié)并將請求轉(zhuǎn)換成對 C++ 服務(wù)器實現(xiàn)有意義的東西。C++ 服務(wù)器方法將執(zhí)行它的功能(即調(diào)用所請求的方法)并使用相同的機(jī)制通過 IIOP 將結(jié)果返回給客戶機(jī)。
RMI 以類似的方式處理請求,但是它使用 JRMP(Java Remote Messaging Protocol,Java 遠(yuǎn)程消息傳遞協(xié)議)作為其傳輸協(xié)議。當(dāng)然,RMI 傳輸還涉及 Java 對象的序列化。
CORBA 和 RMI 的差異
|
遠(yuǎn)程對象定位
CORBA 使用 CosNaming 命名服務(wù)定位遠(yuǎn)程對象。CosNaming 為名稱服務(wù)器保存對 CORBA 服務(wù)器進(jìn)程的綁定(或引用)提供了一個框架。當(dāng) CORBA 客戶機(jī)向名稱服務(wù)發(fā)送 CosNaming 請求,請求給定名稱的服務(wù)器進(jìn)程時,名稱服務(wù)返回該進(jìn)程的可互操作對象引用(interoperable object reference(IOR))。接著,客戶機(jī)使用該 IOR 直接與服務(wù)器進(jìn)程通信。
IOR 包含關(guān)于服務(wù)器進(jìn)程的信息,例如服務(wù)器進(jìn)程的位置。CosNaming 服務(wù)的缺點(diǎn)之一是,IOR 對人類而言是難以看懂的 — 至少對我們這些沒有電子大腦的人來說是這樣。相反地,RMI 對用戶則要友好一些。它使用運(yùn)行在 JNDI 之上的注冊中心(與命名服務(wù)極為相似)來定位遠(yuǎn)程對象。RMI 注冊中心使用 Java Reference
對象(它由若干個 RefAddr
對象組成)來識別和定位遠(yuǎn)程對象。這些 Java 對象比 IOR 對用戶更加友好。
不久前,COBRA 將可互操作命名服務(wù)(Interoperable Naming Service(INS))結(jié)合進(jìn)了它的對象-定位(object-location)模式。INS 在 CosNaming 上運(yùn)行,使用人類可以閱讀的 URL 作它的對象位置。INS 不使用命名服務(wù);相反地,它將調(diào)用直接發(fā)送到指定的 URL。請參閱參考資料了解關(guān)于 INS 的更多信息。
RMI 對 CORBA
那么,哪一個更好呢:是 CORBA 還是 RMI?答案取決于您想做什么。CORBA 是一個運(yùn)行在業(yè)界標(biāo)準(zhǔn)的第三或第四代協(xié)議上的、經(jīng)過試驗和測試的大體系結(jié)構(gòu)。如果考慮到 CORBA 提供的所有附件(例如:事務(wù)處理、安全攔截器、事件通道,還有更多)的話,則 CORBA 看來是企業(yè)應(yīng)用程序的解決方案。CORBA 的最大缺點(diǎn)是它很復(fù)雜。要熟練使用 CORBA,開發(fā)者通常要經(jīng)歷陡峭的培訓(xùn)曲線。
相反地,RMI 相當(dāng)容易學(xué)習(xí)。創(chuàng)建一個客戶機(jī)/服務(wù)器實現(xiàn),綁定到注冊中心和遠(yuǎn)程對象,使用 RMI 調(diào)用和/或接收請求都相當(dāng)簡單。RMI 的資源占用量也比 CORBA 小得多,因為 JRMP 是開銷比 IIOP 小得多的協(xié)議。但是,RMI 缺乏 CORBA 的工業(yè)級的附件,而且是純基于 Java 的機(jī)制。那么,我們真正需要的就是 RMI 的靈活性和易用性以及 CORBA 的企業(yè)就緒性,對嗎?那就開始討論 RMI-IIOP 吧。
為什么是 RMI-IIOP?
|
RMI-IIOP 概覽
RMI-IIOP 讓您僅需極少修改就可以在 IIOP 上運(yùn)行 RMI 調(diào)用。借助于 RMI-IIOP,您可以編寫簡單易懂的 Java 代碼,同時使用 CORBA 提供的豐富的企業(yè)功能套件。而且,代碼的靈活性足夠大,可以運(yùn)行在 RMI 或 IIOP 上。這意味著,您的代碼可以在純 Java 環(huán)境中運(yùn)行(當(dāng)小的資源占用量和靈活性很關(guān)鍵時),或者對代碼作少量修改后集成到現(xiàn)有的 CORBA 基礎(chǔ)架構(gòu)中。
RMI-IIOP 很強(qiáng)大的功能之一是,它讓您編寫純 Java 客戶機(jī)/服務(wù)器實現(xiàn)而不喪失 RMI 類序列化的靈活性。RMI-IIOP 通過覆蓋 Java 序列化并在導(dǎo)線上將 Java 類轉(zhuǎn)換成 IIOP 做到這一點(diǎn)。在另一端,Java 類被作為 IIOP 從導(dǎo)線上讀下來,接著創(chuàng)建這個類的一個新實例(使用反射),類的所有成員的值都完整無缺 — 瞧:這就是 IIOP 上的 Java 序列化!
為了讓 RMI-IIOP 實現(xiàn)透明的對象定位,ORB 供應(yīng)商歷史上曾經(jīng)使用 Java CosNaming 服務(wù)提供者(或用外行人的話說,是插件)。該插件在 JNDI API 之下工作,訪問 CORBA 命名服務(wù)。盡管我沒有在這里花篇幅來說明原因,但這種命名解決方案并不理想。其結(jié)果是,許多供應(yīng)商 — 尤其是應(yīng)用服務(wù)器供應(yīng)商 — 為 RMI-IIOP 開發(fā)了專門的對象定位機(jī)制。
RMI-IIOP 也支持作為 Java CosNaming 服務(wù)的一個擴(kuò)展的 INS。因為我相信 INS 將確定對象定位的未來方向,所以我們在本文將討論的代碼示例使用 INS。
注:因為 Sun 尚未完全遵循 OMG INS 標(biāo)準(zhǔn),也尚未公開 org.omg.CORBA.ORB
接口的 register_initial_reference
,所以本文提供的源代碼將不能與 Sun JDK 一起工作。您將需要 IBM Developer Kit for Java technology,版本 1.3.1 或更高版本。不過,我已經(jīng)創(chuàng)建了一個使用命名服務(wù)的與 Sun 兼容的示例,您可以從參考資料部分下載它。
自己動手構(gòu)建 RMI-IIOP
說得夠多了,讓我們來編寫代碼吧!在以下幾部分中,我們將構(gòu)建一個簡單的、基于 Java 的客戶機(jī)/服務(wù)器 RMI-IIOP 應(yīng)用程序。這個應(yīng)用程序由三個部分組成:RMI 接口、服務(wù)器應(yīng)用程序和客戶機(jī)應(yīng)用程序。示例以在 IIOP 之上的 Java 序列化為特色,所以您可以看到 Java 類如何被客戶機(jī)實例化,如何傳遞到服務(wù)器,由服務(wù)器更改,然后將所有修改完整地回傳到客戶機(jī)。
第 1 部分:定義接口
在 RMI-IIOP 下,我們可以選擇使用 RMI 或 IDL 來定義接口。因為我們想看看 RMI 如何運(yùn)行在 IIOP 上,所以我們將使用 RMI 定義示例接口。清單 1 是我們的簡單示例的 RMI 接口:
|
RMIInterface
定義一個 hello()
方法和一個 alterClass(SerClass)
方法。后一個方法用 SerClass
作參數(shù),SerClass
是一個實現(xiàn) Serializable
的 Java 類,alterClass(SerClass)
方法返回一個類型與其參數(shù)的類型相同的類。SerClass
是一個有幾個成員的簡單的類,每個成員有相應(yīng)的 getter 方法。這些方法如清單 2 所示:
|
這就是我們簡單的接口的全部?,F(xiàn)在我們來研究一下服務(wù)器類。
第 2 部分:構(gòu)建服務(wù)器
我們將使用一個既充當(dāng) RMIInterface
實現(xiàn)類又包含 main 方法(以啟動我們的服務(wù))的服務(wù)器類(Server.java
)。Server.java
繼承 javax.rmi.PortableRemoteObject
。這樣,它就包含了將自己作為 Remote
接口綁定到 ORB 和開始偵聽請求所需要的全部功能。清單 3 是該服務(wù)器的代碼:
|
呃,這里發(fā)生著什么呢?
服務(wù)器應(yīng)用程序的代碼很長,那我們就分開來講吧。首先,如前面提到過的,Server
類實現(xiàn) RMIInterface
并為它的所有方法提供實現(xiàn)。您可以在代碼的前面部分看到 RMIInterface
的 hello()
方法和 alterClass(SerClass)
方法的實現(xiàn)。hello()
方法只是返回字符串“Hello there!”。alterClass(SerClass)
方法用 SerClass
對象作參數(shù),修改成員的值,然后返回新的對象 — 全都通過 RMI-IIOP。
Server.java
的 main 方法初始化一個 ORB。這個 ORB 將設(shè)置為 8080 的 com.ibm.CORBA.ListenerPort
屬性作為參數(shù)傳入。這將使得 ORB 在端口 8080 上偵聽傳入請求。請注意,com.ibm.CORBA.ListenerPort
是一個專有的 IBM 屬性。如果您想在另一供應(yīng)商的 ORB 上運(yùn)行這些代碼,那您應(yīng)該參閱該供應(yīng)商的文檔,找到適當(dāng)?shù)膶傩?。(Sun 使用 com.sun.CORBA.POA.ORBPersistentServerPort
,但它只在您使用 POA(portable object adapter,可移植對象適配器)伺服器(servant)時才能夠工作。)
初始化 ORB 后,main 方法接著對 Server
對象進(jìn)行實例化。因為這個 server 對象也是一個 PortableRemoteObject
,所以缺省構(gòu)造函數(shù)會自動調(diào)用 exportObject(this)
。這個對象現(xiàn)在已經(jīng)就緒于接收遠(yuǎn)程調(diào)用。
接著,我們需要通過調(diào)用 ORB.register_initial_reference(String,orb.omg.CORBA.Object)
注冊這個對象。為此,我們需要把我們的 server 對象作為 org.omg.CORBA.Object
的引用。調(diào)用 PortableRemoteObject.toStub(s)
實現(xiàn)了這一點(diǎn),因為所返回的對象都實現(xiàn)了 java.rmi.Remote
和 org.omg.CORBA.Object
。
然后,返回的 org.omg.CORBA.Object
對象向服務(wù)器端 ORB 注冊為“OurLittleClient”。為了確保 INS 請求能夠定位對象,我們使用注冊調(diào)用 register_initial_reference
。當(dāng) INS 調(diào)用進(jìn)入 ORB 時,ORB 將查找已經(jīng)以正在被請求的名稱注冊的對象。由于我們將對象注冊為“OurLittleClient”,所以,當(dāng)一個 INS 調(diào)用進(jìn)入我們的服務(wù)器 ORB 要求“OurLittleClient”時,我們將知道客戶機(jī)正在查找的是哪個對象。
最后,我確信您已經(jīng)注意到我們將 ORB 強(qiáng)制轉(zhuǎn)型成 com.ibm.CORBA.iiop.ORB
。因為 Sun 尚未公開 org.omg.CORBA.ORB
接口的 register_initial_reference
,所以 IBM SDK 也不能將它公開。因此,我們必須將我們的 ORB 強(qiáng)制轉(zhuǎn)型成 IBM ORB。隨著 Sun 越來越遵循 OMG,JDK 的未來版本(1.4.0 后)將可能不需要這種強(qiáng)制轉(zhuǎn)型。
就是這樣!很簡單吧 — 嗯,是有點(diǎn)。我們的服務(wù)器現(xiàn)在正在等待傳入客戶機(jī) INS 請求。但客戶機(jī)怎么樣呢?
第 3 部分:構(gòu)建客戶機(jī)
客戶機(jī)應(yīng)用程序的代碼如清單 4 所示:
|
如何分解客戶機(jī)代碼
客戶機(jī)代碼比服務(wù)器代碼要簡單一些。我們初始化一個 ORB,然后調(diào)用 string_to_object(String)
,其中的 string 是我們的 INS URL。構(gòu)造 INS URL 相當(dāng)簡單:首先,我們指定我們使用 corbaloc URL(請參閱參考資料)和 IIOP 協(xié)議版本 1.2。接著,我們將主機(jī)名(www.whatever.com)和要連接的端口添加進(jìn)去。最后,我們指定我們要查找的服務(wù)的名稱。結(jié)果 INS URL 是 corbaloc:iiop:1.2@localhost:8080/OurLittleClient。
當(dāng)我們將這個 URL 傳遞到 ORB.string_to_object(String)
時,ORB 將分派一個請求到所指定的服務(wù)器,以請求所請求的服務(wù)。假設(shè)一切運(yùn)轉(zhuǎn)正常,則 ORB 將接收回該服務(wù)的一個對象引用(實際上是一個 IOR)。然后,我們將該對象引用強(qiáng)制轉(zhuǎn)型(narrow)成我們能夠使用的東西,即 RMIInterface
,這樣,我們就為開始調(diào)用方法做好了準(zhǔn)備。
在調(diào)用了簡單的 hello 方法(它應(yīng)該不需要任何解釋吧)之后,我們可以開始探討 RMI-IIOP 的序列化功能了。首先,我們創(chuàng)建一個 SerClass
,一個可序列化的 Java 類,并初始化它的成員變量。接著,我們將這個類傳入到我們的方法,方法通過 IIOP 將類寫出到服務(wù)器。服務(wù)器讀入類并將它重創(chuàng)建為服務(wù)器端 Java 對象,修改它的成員值,然后返回它(使用 IIOP)作為方法的返回值。當(dāng)接收到在遠(yuǎn)程方法調(diào)用之后重創(chuàng)建的對象時,我們看到它的成員確實已被服務(wù)器修改了。就是這么簡單:在 IIOP 上進(jìn)行 Java 序列化。
第 4 部分:運(yùn)行示例
請注意,我們這里所創(chuàng)建的示例必須在 IBM Developer Kit for Java technology,版本 1.3.1 或更高版本中運(yùn)行。如果您寧愿使用 Sun JDK,請下載特定于 Sun 的源代碼,您應(yīng)該在 Sun 1.4.0 JDK 或更高版本中運(yùn)行它。這個源代碼包括一個解釋 IBM SDK 版本和 Sun JDK 版本之間的差異的 readme.txt 文件。如果您沒有 IBM Developer Kit for Java technology(而您又想要一個),請現(xiàn)在就下載一個;它們是免費(fèi)的。
這里是運(yùn)行示例的步驟:
javac *.java
,javac 所有文件。rmic
(帶 IIOP 標(biāo)志):rmic -iiop Server
。start java Server
。start java Client
。 關(guān)于 RMI-IIOP 和 EJB 組件的一點(diǎn)注釋
EJB 2.0 規(guī)范指出,EJB 組件必須能在 RMI 和 RMI-IIOP 上運(yùn)行。添加 RMI-IIOP 作為針對 EJB 組件的在線協(xié)議,已經(jīng)給將 J2EE 環(huán)境集成到現(xiàn)有的企業(yè)基礎(chǔ)設(shè)施(多數(shù)是 CORBA 相當(dāng)密集的)帶來了很大幫助。但它也引起了一些問題。
簡單地說,就是將定制構(gòu)建的組件和 EJB 組件集成起來要求您(開發(fā)者)處理管道(plumbing),否則在 EJB 體系結(jié)構(gòu)中它們對您來說將很抽象。到目前為止,還沒有解決這個問題的簡單方案,可能永遠(yuǎn)也不會有。隨著諸如 Web 服務(wù)這樣的技術(shù)的發(fā)展,或許會出現(xiàn)解決方案,但目前尚未可知。
結(jié)束語:此后該做什么
我希望本文已經(jīng)向您展示了構(gòu)建和運(yùn)行 RMI-IIOP 客戶機(jī)/服務(wù)器應(yīng)用程序是多么容易。您可以修改一下我們使用的示例,用純 CORBA 替代客戶機(jī)或服務(wù)器,不過這樣做將除去您應(yīng)用程序中的 Java 序列化。
如果您想在 CORBA 環(huán)境中使用 RMI-IIOP,那么看看 IDL 如何映射成 Java 以及 Java 如何映射成 IDL 是值得的。如果您想在不安全的環(huán)境(即不是您自己的 PC)中部署 RMI-IIOP,那么研究一下 CORBA 安全功能(如攔截器和 CORBA 安全模型)以及其它 CORBA 企業(yè)功能(如事務(wù)處理)是個不錯的主意。CORBA 所有的豐富功能在您運(yùn)行 RMI-IIOP 時都可以使用。