<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 4,  comments - 9,  trackbacks - 0

    遠(yuǎn)程方法調(diào)用入門(mén)指南(Java RMI Tutorial)


    Java R MI Tutorial

    遠(yuǎn)程方法調(diào)用入門(mén)指南

    Copyright ? 2005 Stephen Suen. All rights reserved.

    Java 遠(yuǎn)程方法調(diào)用(Remote Method Invocation, RMI)使得運(yùn)行在一個(gè) Java 虛擬機(jī)(Java Virtual Machine, JVM)的對(duì)象可以調(diào)用運(yùn)行另一個(gè) JVM 之上的其他對(duì)象的方法,從而提供了程序間進(jìn)行遠(yuǎn)程通訊的途徑。RMI 是 J2EE 的很多分布式技術(shù)的基礎(chǔ),比如 RMI-IIOP 乃至 EJB。本文是 RMI 的一個(gè)入門(mén)指南,目的在于幫助讀者快速建立對(duì) Java RMI 的一個(gè)感性認(rèn)識(shí),以便進(jìn)行更深層次的學(xué)習(xí)。事實(shí)上,如果你了解 RMI 的目的在于更好的理解和學(xué)習(xí) EJB,那么本文就再合適不過(guò)了。通過(guò)本文所了解的 RMI 的知識(shí)和技巧,應(yīng)該足夠服務(wù)于這個(gè)目的了。

    本文的最新版本將發(fā)布在程序員咖啡館網(wǎng)站上(建設(shè)中)。歡迎訂閱我們的郵件組,以獲得關(guān)于本文的正式發(fā)布及更新信息。

    全文在保證完整性,且保留全部版權(quán)聲明(包括上述鏈接)的前提下可以在任意媒體轉(zhuǎn)載——須保留此標(biāo)注。


    1. 簡(jiǎn)介

    我們知道遠(yuǎn)程過(guò)程調(diào)用(Remote Procedure Call, RPC)可以用于一個(gè)進(jìn)程調(diào)用另一個(gè)進(jìn)程(很可能在另一個(gè)遠(yuǎn)程主機(jī)上)中的過(guò)程,從而提供了過(guò)程的分布能力。Java 的 RMI 則在 RPC 的基礎(chǔ)上向前又邁進(jìn)了一步,即提供分布式 對(duì)象間的通訊,允許我們獲得在遠(yuǎn)程進(jìn)程中的對(duì)象(稱(chēng)為遠(yuǎn)程對(duì)象)的引用(稱(chēng)為遠(yuǎn)程引用),進(jìn)而通過(guò)引用調(diào)用遠(yuǎn)程對(duì)象的方法,就好像該對(duì)象是與你的客戶端代碼同樣運(yùn)行在本地進(jìn)程中一樣。RMI 使用了術(shù)語(yǔ)"方法"(Method)強(qiáng)調(diào)了這種進(jìn)步,即在分布式基礎(chǔ)上,充分支持面向?qū)ο蟮奶匦浴?/p>

    RMI 并不是 Java 中支持遠(yuǎn)程方法調(diào)用的唯一選擇。在 RMI 基礎(chǔ)上發(fā)展而來(lái)的 RMI-IIOP(Java Remote Method Invocation over the Internet Inter-ORB Protocol),不但繼承了 RMI 的大部分優(yōu)點(diǎn),并且可以兼容于 CORBA。J2EE 和 EJB 都要求使用 RMI-IIOP 而不是 RMI。盡管如此,理解 RMI 將大大有助于 RMI-IIOP 的理解。所以,即便你的興趣在 RMI-IIOP 或者 EJB,相信本文也會(huì)對(duì)你很有幫助。另外,如果你現(xiàn)在就對(duì) API 感興趣,那么可以告訴你,RMI 使用 java.rmi 包,而 RMI-IIOP 則既使用 java.rmi 也使用擴(kuò)展的 javax.rmi 包。

    本文的隨后內(nèi)容將僅針對(duì) Java RMI。

    2. 分布式對(duì)象

    在學(xué)習(xí) RMI 之前,我們需要了解一些基礎(chǔ)知識(shí)。首先需要了解所謂的分布式對(duì)象(Distributed Object)。分布式對(duì)象是指一個(gè)對(duì)象可以被遠(yuǎn)程系統(tǒng)所調(diào)用。對(duì)于 Java 而言,即對(duì)象不僅可以被同一虛擬機(jī)中的其他客戶程序(Client)調(diào)用,也可以被運(yùn)行于其他虛擬機(jī)中的客戶程序調(diào)用,甚至可以通過(guò)網(wǎng)絡(luò)被其他遠(yuǎn)程主機(jī)之上的客戶程序調(diào)用。

    下面的圖示說(shuō)明了客戶程序是如何調(diào)用分布式對(duì)象的:

    從圖上我們可以看到,分布式對(duì)象被調(diào)用的過(guò)程是這樣的:

    1. 客戶程序調(diào)用一個(gè)被稱(chēng)為 Stub (有時(shí)譯作存根,為了不產(chǎn)生歧義,本文將使用其英文形式)的客戶端代理對(duì)象。該代理對(duì)象負(fù)責(zé)對(duì)客戶端隱藏網(wǎng)絡(luò)通訊的細(xì)節(jié)。Stub 知道如何通過(guò)網(wǎng)絡(luò)套接字(Socket)發(fā)送調(diào)用,包括如何將調(diào)用參數(shù)轉(zhuǎn)換為適當(dāng)?shù)男问揭员銈鬏數(shù)取?/p>

    2. Stub 通過(guò)網(wǎng)絡(luò)將調(diào)用傳遞到服務(wù)器端,也就是分布對(duì)象一端的一個(gè)被稱(chēng)為 Skeleton 的代理對(duì)象。同樣,該代理對(duì)象負(fù)責(zé)對(duì)分布式對(duì)象隱藏網(wǎng)絡(luò)通訊的細(xì)節(jié)。Skeleton 知道如何從網(wǎng)絡(luò)套接字(Socket)中接受調(diào)用,包括如何將調(diào)用參數(shù)從網(wǎng)絡(luò)傳輸形式轉(zhuǎn)換為 Java 形式等。

    3. Skeleton 將調(diào)用傳遞給分布式對(duì)象。分布式對(duì)象執(zhí)行相應(yīng)的調(diào)用,之后將返回值傳遞給 Skeleton,進(jìn)而傳遞到 Stub,最終返回給客戶程序。

    這個(gè)場(chǎng)景基于一個(gè)基本的法則,即行為的定義和行為的具體實(shí)現(xiàn)相分離。如圖所示,客戶端代理對(duì)象 Stub 和分布式對(duì)象都實(shí)現(xiàn)了相同的接口,該接口稱(chēng)為遠(yuǎn)程接口(Remote Interface)。正是該接口定義了行為,而分布式對(duì)象本身則提供具體的實(shí)現(xiàn)。對(duì)于 Java RMI 而言,我們用接口(interface)定義行為,用類(lèi)(class)定義實(shí)現(xiàn)。

    3. RMI 架構(gòu)

    RMI 的底層架構(gòu)由三層構(gòu)成:

    • 首先是 Stub/Skeleton 層。該層提供了客戶程序和服務(wù)程序彼此交互的接口。

    • 然后是遠(yuǎn)程引用(Remote Reference)層。這一層相當(dāng)于在其之上的 Stub/Skeleton 層和在其之下的傳輸協(xié)議層之前的中間件,負(fù)責(zé)處理遠(yuǎn)程對(duì)象引用的創(chuàng)建和管理。

    • 最后是傳輸協(xié)議(Transport Protocol) 層。該層提供了數(shù)據(jù)協(xié)議,用以通過(guò)線路傳輸客戶程序和遠(yuǎn)程對(duì)象間的請(qǐng)求和應(yīng)答。

    這些層之間的交互可以參照下面的示意圖:

    和其它分布式對(duì)象機(jī)制一樣,Java RMI 的客戶程序使用客戶端的 Stub 向遠(yuǎn)程對(duì)象請(qǐng)求方法調(diào)用;服務(wù)器對(duì)象則通過(guò)服務(wù)器端的 Skeleton 接受請(qǐng)求。我們深入進(jìn)去,來(lái)看看其中的一些細(xì)節(jié)。

    注意: 事實(shí)上,在 Java 1.2 之后,RMI 不再需要 Skeleton 對(duì)象,而是通過(guò) Java 的反射機(jī)制(Reflection)來(lái)完成對(duì)服務(wù)器端的遠(yuǎn)程對(duì)象的調(diào)用。為了便于說(shuō)明問(wèn)題,本文以下內(nèi)容仍然基于 Skeleton 來(lái)講解。

    當(dāng)客戶程序調(diào)用 Stub 時(shí),Stub 負(fù)責(zé)將方法的參數(shù)轉(zhuǎn)換為序列化(Serialized)形式,我們使用一個(gè)特殊的術(shù)語(yǔ),即編列(Marshal)來(lái)指代這個(gè)過(guò)程。編列的目的是將這些參數(shù)轉(zhuǎn)換為可移植的形式,從而可以通過(guò)網(wǎng)絡(luò)傳輸?shù)竭h(yuǎn)程的服務(wù)對(duì)象一端。不幸的是,這個(gè)過(guò)程沒(méi)有想象中那么簡(jiǎn)單。這里我們首先要理解一個(gè)經(jīng)典的問(wèn)題,即方法調(diào)用時(shí),參數(shù)究竟是傳值還是傳引用呢?對(duì)于 Java RMI 來(lái)說(shuō),存在四種情況,我們將分別加以說(shuō)明。

    • 對(duì)于基本的原始類(lèi)型(整型,字符型等等),將被自動(dòng)的序列化,以傳值的方式編列。

    • 對(duì)于 Java 的對(duì)象,如果該對(duì)象是可序列化的(實(shí)現(xiàn)了 java.io.Serializable 接口),則通過(guò) Java 序列化機(jī)制自動(dòng)地加以序列化,以傳值的方式編列。對(duì)象之中包含的原始類(lèi)型以及所有被該對(duì)象引用,且沒(méi)有聲明為 transient 的對(duì)象也將自動(dòng)的序列化。當(dāng)然,這些被引用的對(duì)象也必須是可序列化的。

    • 絕大多數(shù)內(nèi)建的 Java 對(duì)象都是可序列化的。 對(duì)于不可序列化的 Java 對(duì)象(java.io.File 最典型),或者對(duì)象中包含對(duì)不可序列化,且沒(méi)有聲明為 transient 的其它對(duì)象的引用。則編列過(guò)程將向客戶程序拋出異常,而宣告失敗。

    • 客戶程序可以調(diào)用遠(yuǎn)程對(duì)象,沒(méi)有理由禁止調(diào)用參數(shù)本身也是遠(yuǎn)程對(duì)象(實(shí)現(xiàn)了 java.rmi.Remote 接口的類(lèi)的實(shí)例)。此時(shí),RMI 采用一種模擬的傳引用方式(當(dāng)然不是傳統(tǒng)意義的傳引用,因?yàn)楸镜貙?duì)內(nèi)存的引用到了遠(yuǎn)程變得毫無(wú)意義),而不是將參數(shù)直接編列復(fù)制到遠(yuǎn)程。這種情況下,交互的雙方發(fā)生的戲劇性變化值得我們注意。參數(shù)是遠(yuǎn)程對(duì)象,意味著該參數(shù)對(duì)象可以遠(yuǎn)程調(diào)用。當(dāng)客戶程序指定遠(yuǎn)程對(duì)象作為參數(shù)調(diào)用服務(wù)器端遠(yuǎn)程對(duì)象的方法時(shí),RMI 的運(yùn)行時(shí)機(jī)制將向服務(wù)器端的遠(yuǎn)程對(duì)象發(fā)送作為參數(shù)的遠(yuǎn)程對(duì)象的一個(gè) Stub 對(duì)象。這樣服務(wù)器端的遠(yuǎn)程對(duì)象就可以回調(diào)(Callback)這個(gè) Stub 對(duì)象的方法,進(jìn)而調(diào)用在客戶端的遠(yuǎn)程對(duì)象的對(duì)應(yīng)方法。通過(guò)這種方法,服務(wù)器端的遠(yuǎn)程對(duì)象就可以修改作為參數(shù)的客戶端遠(yuǎn)程對(duì)象的內(nèi)部狀態(tài),這正是傳統(tǒng)意義的傳引用所具備的特性。是不是有點(diǎn)暈?這里的關(guān)鍵是要明白,在分布式環(huán)境中,所謂服務(wù)器和客戶端都是相對(duì)的。被請(qǐng)求的一方就是服務(wù)器,而發(fā)出請(qǐng)求的一方就是客戶端。

    在調(diào)用參數(shù)的編列過(guò)程成功后,客戶端的遠(yuǎn)程引用層從 Stub 那里獲得了編列后的參數(shù)以及對(duì)服務(wù)器端遠(yuǎn)程對(duì)象的遠(yuǎn)程引用(參見(jiàn) java.rmi.server.RemoteRef API)。該層負(fù)責(zé)將客戶程序的請(qǐng)求依據(jù)底層的 RMI 數(shù)據(jù)傳輸協(xié)議轉(zhuǎn)換為傳輸層請(qǐng)求。在 RMI 中,有多種的可能的傳輸機(jī)制,比如點(diǎn)對(duì)點(diǎn)(Point-to-Point)以及廣播(Multicast)等。不過(guò),在當(dāng)前的 JMI 版本中只支持點(diǎn)對(duì)點(diǎn)協(xié)議,即遠(yuǎn)程引用層將生成唯一的傳輸層請(qǐng)求,發(fā)往指定的唯一遠(yuǎn)程對(duì)象(參見(jiàn) java.rmi.server.UnicastRemoteObject API)。

    在服務(wù)器端,服務(wù)器端的遠(yuǎn)程引用層接收傳輸層請(qǐng)求,并將其轉(zhuǎn)換為對(duì)遠(yuǎn)程對(duì)象的服務(wù)器端代理對(duì)象 Skeleton 的調(diào)用。Skeleton 對(duì)象負(fù)責(zé)將請(qǐng)求轉(zhuǎn)換為對(duì)實(shí)際的遠(yuǎn)程對(duì)象的方法調(diào)用。這是通過(guò)與編列過(guò)程相對(duì)的反編列(Unmarshal)過(guò)程實(shí)現(xiàn)的。所有序列化的參數(shù)被轉(zhuǎn)換為 Java 形式,其中作為參數(shù)的遠(yuǎn)程對(duì)象(實(shí)際上發(fā)送的是遠(yuǎn)程引用)被轉(zhuǎn)換為服務(wù)器端本地的 Stub 對(duì)象。

    如果方法調(diào)用有返回值或者拋出異常,則 Skeleton 負(fù)責(zé)編列返回值或者異常,通過(guò)服務(wù)器端的遠(yuǎn)程引用層,經(jīng)傳輸層傳遞給客戶端;相應(yīng)地,客戶端的遠(yuǎn)程引用層和 Stub 負(fù)責(zé)反編列并最終將結(jié)果返回給客戶程序。

    整個(gè)過(guò)程中,可能最讓人迷惑的是遠(yuǎn)程引用層。這里只要明白,本地的 Stub 對(duì)象是如何產(chǎn)生的,就不難理解遠(yuǎn)程引用的意義所在了。遠(yuǎn)程引用中包含了其所指向的遠(yuǎn)程對(duì)象的信息,該遠(yuǎn)程引用將用于構(gòu)造作為本地代理對(duì)象的 Stub 對(duì)象。構(gòu)造后,Stub 對(duì)象內(nèi)部將維護(hù)該遠(yuǎn)程引用。真正在網(wǎng)絡(luò)上傳輸?shù)膶?shí)際上就是這個(gè)遠(yuǎn)程引用,而不是 Stub 對(duì)象。

    4. RMI 對(duì)象服務(wù)

    在 RMI 的基本架構(gòu)之上,RMI 提供服務(wù)與分布式應(yīng)用程序的一些對(duì)象服務(wù),包括對(duì)象的命名/注冊(cè)(Naming/Registry)服務(wù),遠(yuǎn)程對(duì)象激活(Activation)服務(wù)以及分布式垃圾收集(Distributed Garbage Collection, DGC)。作為入門(mén)指南,本文將指介紹其中的命名/注冊(cè)服務(wù),因?yàn)樗菍?shí)戰(zhàn) RMI 所必備的。其它內(nèi)容請(qǐng)讀者自行參考其它更加深入的資料。

    在前一節(jié)中,如果你喜歡刨根問(wèn)底,可能已經(jīng)注意到,客戶端要調(diào)用遠(yuǎn)程對(duì)象,是通過(guò)其代理對(duì)象 Stub 完成的,那么 Stub 最早是從哪里得來(lái)的呢?RMI 的命名/注冊(cè)服務(wù)正是解決這一問(wèn)題的。當(dāng)服務(wù)器端想向客戶端提供基于 RMI 的服務(wù)時(shí),它需要將一個(gè)或多個(gè)遠(yuǎn)程對(duì)象注冊(cè)到本地的 RMI 注冊(cè)表中(參見(jiàn)java.rmi.registry.Registry API)。每個(gè)對(duì)象在注冊(cè)時(shí)都被指定一個(gè)將來(lái)用于客戶程序引用該對(duì)象的名稱(chēng)。客戶程序通過(guò)命名服務(wù)(參見(jiàn) java.rmi.Naming API),指定類(lèi)似 URL 的對(duì)象名稱(chēng)就可以獲得指向遠(yuǎn)程對(duì)象的遠(yuǎn)程引用。在 Naming 中的 lookup() 方法找到遠(yuǎn)程對(duì)象所在的主機(jī)后,它將檢索該主機(jī)上的 RMI 注冊(cè)表,并請(qǐng)求所需的遠(yuǎn)程對(duì)象。如果注冊(cè)表發(fā)現(xiàn)被請(qǐng)求的遠(yuǎn)程對(duì)象,它將生成一個(gè)對(duì)該遠(yuǎn)程對(duì)象的遠(yuǎn)程引用,并將其返回給客戶端,客戶端則基于遠(yuǎn)程引用生成相應(yīng)的 Stub 對(duì)象,并將引用傳遞給調(diào)用者。之后,雙方就可以按照我們前面講過(guò)的方式進(jìn)行交互了。

    注意: RMI 命名服務(wù)提供的 Naming 類(lèi)并不是你的唯一選擇。RMI 的注冊(cè)表可以與其他命名服務(wù)綁定,比如 JNDI,這樣你就可以通過(guò) JNDI 來(lái)訪問(wèn) RMI 的注冊(cè)表了。

    5. 實(shí)戰(zhàn) RMI

    理論離不開(kāi)實(shí)踐,理解 RMI 的最好辦法就是通過(guò)例子。開(kāi)發(fā) RMI 的分布式對(duì)象的大體過(guò)程包括如下幾步:

    1. 定義遠(yuǎn)程接口。這一步是通過(guò)擴(kuò)展 java.rmi.Remote 接口,并定義所需的業(yè)務(wù)方法實(shí)現(xiàn)的。

    2. 定義遠(yuǎn)程接口的實(shí)現(xiàn)類(lèi)。即實(shí)現(xiàn)上一步所定義的接口,給出業(yè)務(wù)方法的具體實(shí)現(xiàn)邏輯。

    3. 編譯遠(yuǎn)程接口和實(shí)現(xiàn)類(lèi),并通過(guò) RMI 編譯器 rmic 基于實(shí)現(xiàn)類(lèi)生成所需的 Stub 和 Skeleton 類(lèi)。

    RMI 中各個(gè)組件之間的關(guān)系如下面這個(gè)示意圖所示:

    回憶我們上一節(jié)所講的,Stub 和 Skeleton 負(fù)責(zé)代理客戶和服務(wù)器之間的通訊。但我們并不需要自己生成它們,相反,RMI 的編譯器 rmic 可以幫我們基于遠(yuǎn)程接口和實(shí)現(xiàn)類(lèi)生成這些類(lèi)。當(dāng)客戶端對(duì)象通過(guò)命名服務(wù)向服務(wù)器端的 RMI 注冊(cè)表請(qǐng)求遠(yuǎn)程對(duì)象時(shí),RMI 將自動(dòng)構(gòu)造對(duì)應(yīng)遠(yuǎn)程對(duì)象的 Skeleton 實(shí)例對(duì)象,并通過(guò) Skeleton 對(duì)象將遠(yuǎn)程引用返回給客戶端。在客戶端,該遠(yuǎn)程引用將用于構(gòu)造 Stub 類(lèi)的實(shí)例對(duì)象。之后,Stub 對(duì)象和 Skeleton 對(duì)象就可以代理客戶對(duì)象和遠(yuǎn)程對(duì)象之間的交互了。

    我們的例子展現(xiàn)了一個(gè)簡(jiǎn)單的應(yīng)用場(chǎng)景。服務(wù)器端部署了一個(gè)計(jì)算引擎,負(fù)責(zé)接受來(lái)自客戶端的計(jì)算任務(wù),在服務(wù)器端執(zhí)行計(jì)算任務(wù),并將結(jié)果返回給客戶端。客戶端將發(fā)送并調(diào)用計(jì)算引擎的計(jì)算任務(wù)實(shí)際上是計(jì)算指定精度的 π 值。

    重要: 本文的例子改編自 The Java? Tutorial Trail:RMI。所有權(quán)利屬于相應(yīng)的所有人。

    6. 定義遠(yuǎn)程接口

    定義遠(yuǎn)程接口與非分布式應(yīng)用中定義接口的方法沒(méi)有太多的區(qū)別。只要遵守下面兩個(gè)要求:

    • 遠(yuǎn)程接口必須直接或者間接的擴(kuò)展自 java.rmi.Remote 接口。遠(yuǎn)程接口還可以在擴(kuò)展該接口的基礎(chǔ)上,同時(shí)擴(kuò)展其它接口,只要被擴(kuò)展的接口的所有方法與遠(yuǎn)程接口的所有方法一樣滿足下一個(gè)要求。

    • 在遠(yuǎn)程接口或者其超接口(Super-interface)中聲明的方法必須滿足下列對(duì)遠(yuǎn)程方法的要求:

      • 遠(yuǎn)程方法必須聲明拋出 java.rmi.RemoteException 異常,或者該異常的超類(lèi)(Superclass),比如 java.io.IOException 或者 java.lang.Exception 異常。在此基礎(chǔ)上,遠(yuǎn)程方法可以聲明拋出應(yīng)用特定的其它異常。

      • 在遠(yuǎn)程方法聲明中,作為參數(shù)或者返回值的遠(yuǎn)程對(duì)象,或者包含在其它非遠(yuǎn)程對(duì)象中的遠(yuǎn)程對(duì)象,必須聲明為其對(duì)應(yīng)的遠(yuǎn)程接口,而不是實(shí)際的實(shí)現(xiàn)類(lèi)。

    注意: 在 Java 1.2 之前,上面關(guān)于拋出異常的要求更嚴(yán)格,即必須拋出 java.rmi.RemoteExcption,不允許類(lèi)似 java.io.IOException 這樣的超類(lèi)。現(xiàn)在之所以放寬了這一要求,是希望可以使定義既可以用于遠(yuǎn)程對(duì)象,也可以用于本地對(duì)象的接口變得容易一些(想想 EJB 中的本地接口和遠(yuǎn)程接口)。當(dāng)然,這并沒(méi)有使問(wèn)題好多少,你還是必須聲明異常。不過(guò),一種觀點(diǎn)認(rèn)為這不是問(wèn)題,強(qiáng)制聲明異常可以使開(kāi)發(fā)人員保持清醒的頭腦,因?yàn)檫h(yuǎn)程對(duì)象和本地對(duì)象在調(diào)用時(shí)傳參的語(yǔ)意是不同的。本地對(duì)象是傳引用,而遠(yuǎn)程對(duì)象主要是傳值,這意味對(duì)參數(shù)內(nèi)部狀態(tài)的修改產(chǎn)生的結(jié)果是不同的。

    對(duì)于第一個(gè)要求,java.rmi.Remote 接口實(shí)際上沒(méi)有任何方法,而只是用作標(biāo)記接口。RMI 的運(yùn)行環(huán)境依賴該接口判斷對(duì)象是否是遠(yuǎn)程對(duì)象。第二個(gè)要求則是因?yàn)榉植际綉?yīng)用可能發(fā)生任何問(wèn)題,比如網(wǎng)絡(luò)問(wèn)題等等。

    例 1 列出了我們的遠(yuǎn)程接口定義。該接口只有一個(gè)方法:executeTask() 用以執(zhí)行指定的計(jì)算任務(wù),并返回相應(yīng)的結(jié)果。注意,我們用后綴 Remote 表明接口是遠(yuǎn)程接口。

    例 1. ComputeEngineRemote 遠(yuǎn)程接口

    package rmitutorial;
    
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    
    public interface ComputeEngineRemote extends Remote {
        public Object executeTask(Task task) throws RemoteException;    
    }

    例 2 列出了計(jì)算任務(wù)接口的定義。該接口也只有一個(gè)方法:execute() 用以執(zhí)行實(shí)際的計(jì)算邏輯,并返回結(jié)果。注意,該接口不是遠(yuǎn)程接口,所以沒(méi)有擴(kuò)展 java.rmi.Remote 接口;其方法也不必拋出 java.rmi.RemoteException 異常。但是,因?yàn)樗鼘⒂米鬟h(yuǎn)程方法的參數(shù),所以擴(kuò)展了 java.io.Serializable 接口。

    例 2. Task 接口

    package rmitutorial;
    
    import java.io.Serializable;
    
    public interface Task extends Serializable {
        Object execute();
    }

    7. 實(shí)現(xiàn)遠(yuǎn)程接口

    接下來(lái),我們將實(shí)現(xiàn)前面定義的遠(yuǎn)程接口。例 3給出了實(shí)現(xiàn)的源代碼。

    例 3. ComputeEngine 實(shí)現(xiàn)

    package rmitutorial;
    
    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    
    public class ComputeEngine extends UnicastRemoteObject 
            implements ComputeEngineRemote {
        
        public ComputeEngine() throws RemoteException {
            super();
        }
        
        public Object executeTask(Task task) throws RemoteException {
            return task.execute();
        }
    }

    類(lèi) ComputeEngine 實(shí)現(xiàn)了之前定義的遠(yuǎn)程接口,同時(shí)繼承自 java.rmi.server.UnicastRemoteObject 超類(lèi)。UnicastRemoteObject 類(lèi)是一個(gè)便捷類(lèi),它實(shí)現(xiàn)了我們前面所講的基于 TCP/IP 的點(diǎn)對(duì)點(diǎn)通訊機(jī)制。遠(yuǎn)程對(duì)象都必須從該類(lèi)擴(kuò)展(除非你想自己實(shí)現(xiàn)幾乎所有 UnicastRemoteObject 的方法)。在我們的實(shí)現(xiàn)類(lèi)的構(gòu)造函數(shù)中,調(diào)用了超類(lèi)的構(gòu)造函數(shù)(當(dāng)然,即使你不顯式的調(diào)用這個(gè)構(gòu)建函數(shù),它也一樣會(huì)被調(diào)用。這里這樣做,只是為了突出強(qiáng)調(diào)這種調(diào)用而已)。該構(gòu)造函數(shù)的最重要的意義就是調(diào)用 UnicastRemoteObject 類(lèi)的 exportObject() 方法。導(dǎo)出(Export)對(duì)象是指使遠(yuǎn)程對(duì)象準(zhǔn)備就緒,可以接受進(jìn)來(lái)的調(diào)用的過(guò)程。而這個(gè)過(guò)程的最重要內(nèi)容就是建立服務(wù)器套接字,監(jiān)聽(tīng)特定的端口,等待客戶端的調(diào)用請(qǐng)求。

    8. 引導(dǎo)程序

    為了讓客戶程序可以找到我們的遠(yuǎn)程對(duì)象,就需要將我們的遠(yuǎn)程對(duì)象注冊(cè)到 RMI 的注冊(cè)表。這個(gè)過(guò)程有時(shí)被稱(chēng)為"引導(dǎo)"過(guò)程(Bootstrap)。我們將為此編寫(xiě)一個(gè)獨(dú)立的引導(dǎo)程序負(fù)責(zé)創(chuàng)建和注冊(cè)遠(yuǎn)程對(duì)象。例 4 給出了引導(dǎo)程序的源代碼。

    例 4. 引導(dǎo)程序

    package rmitutorial;
    
    import java.rmi.Naming;
    import java.rmi.RMISecurityManager;
    
    public class Bootstrap {
        
        public static void main(String[] args) throws Exception {
            String name = "ComputeEngine";
            
            ComputeEngine engine = new ComputeEngine();
            System.out.println("ComputerEngine exported");
            
            Naming.rebind(name, engine);
            System.out.println("ComputeEngine bound");
        }
    }
    

    可以看到,我們首先創(chuàng)建了一個(gè)遠(yuǎn)程對(duì)象(同時(shí)導(dǎo)出了該對(duì)象),之后將該對(duì)象綁定到 RMI 注冊(cè)表中。Namingrebind() 方法接受一個(gè) URL 形式的名字作綁定之用。其完整格式如下:

    protocol://host:port/object

    其中,協(xié)議(Protocol)默認(rèn)為 rmi;主機(jī)名默認(rèn)為 localhost;端口默認(rèn)為 1099。注意,JDK 中提供的默認(rèn) Naming 實(shí)現(xiàn)只支持 rmi 協(xié)議。在我們的引導(dǎo)程序里面只給出了對(duì)象綁定的名字,而其它部分均使用缺省值。

    9. 客戶端程序

    例 5 給出了我們的客戶端程序。該程序接受兩個(gè)參數(shù),分別是遠(yuǎn)程對(duì)象所在的主機(jī)地址和希望獲得的 π 值的精度。

    例 5. Client.java

    package rmitutorial;
    
    import java.math.BigDecimal;
    import java.rmi.Naming;
    
    public class Client {
        public static void main(String args[]) throws Exception {
                String name = "rmi://" + args[0] + "/ComputeEngine";
                ComputeEngineRemote engineRemote = 
                        (ComputeEngineRemote)Naming.lookup(name);
                Pi task = new Pi(Integer.parseInt(args[1]));
                BigDecimal pi = (BigDecimal)(engineRemote.executeTask(task));
                System.out.println(pi);
        }
    }

    例 6. Pi.java

    package rmitutorial;
    
    import java.math.*;
    
    public class Pi implements Task {
        
        private static final BigDecimal ZERO =
                BigDecimal.valueOf(0);
        private static final BigDecimal  ONE =
                BigDecimal.valueOf(1);
        private static final BigDecimal FOUR =
                BigDecimal.valueOf(4);
        
        private static final int roundingMode =
                BigDecimal.ROUND_HALF_EVEN;
        
        private int digits;
        
        public Pi(int digits) {
            this.digits = digits;
        }
        
        public Object execute() {
            return computePi(digits);
        }
        
        public static BigDecimal computePi(int digits) {
            int scale = digits + 5;
            BigDecimal arctan1_5 = arctan(5, scale);
            BigDecimal arctan1_239 = arctan(239, scale);
            BigDecimal pi = arctan1_5.multiply(FOUR).subtract(
                    arctan1_239).multiply(FOUR);
            return pi.setScale(digits,
                    BigDecimal.ROUND_HALF_UP);
        }
    
        public static BigDecimal arctan(int inverseX,
                int scale) {
            BigDecimal result, numer, term;
            BigDecimal invX = BigDecimal.valueOf(inverseX);
            BigDecimal invX2 =
                    BigDecimal.valueOf(inverseX * inverseX);
            
            numer = ONE.divide(invX, scale, roundingMode);
            
            result = numer;
            int i = 1;
            do {
                numer =
                        numer.divide(invX2, scale, roundingMode);
                int denom = 2 * i + 1;
                term =
                        numer.divide(BigDecimal.valueOf(denom),
                        scale, roundingMode);
                if ((i % 2) != 0) {
                    result = result.subtract(term);
                } else {
                    result = result.add(term);
                }
                i++;
            } while (term.compareTo(ZERO) != 0);
            return result;
        }
    }

    10. 編譯示例程序

    編譯我們的示例程序和編譯其它非分布式的應(yīng)用沒(méi)什么區(qū)別。只是編譯之后,需要使用 RMI 編譯器,即 rmic 生成所需 Stub 和 Skeleton 實(shí)現(xiàn)。使用 rmic 的方式是將我們的遠(yuǎn)程對(duì)象的實(shí)現(xiàn)類(lèi)(不是遠(yuǎn)程接口)的全類(lèi)名作為參數(shù)來(lái)運(yùn)行 rmic 命令。參考下面的示例:

    E:\classes\rmic rmitutorial.ComputeEngine

    編譯之后將生成 rmitutorial.ComputeEngine_Skelrmitutorial.ComputeEngine_Stub 兩個(gè)類(lèi)。

    11. 運(yùn)行示例程序

    遠(yuǎn)程對(duì)象的引用通常是通過(guò) RMI 的注冊(cè)表服務(wù)以及 java.rmi.Naming 接口獲得的。遠(yuǎn)程對(duì)象需要導(dǎo)出(注冊(cè))相應(yīng)的遠(yuǎn)程引用到注冊(cè)表服務(wù),之后注冊(cè)表服務(wù)就可以監(jiān)聽(tīng)并服務(wù)于客戶端對(duì)遠(yuǎn)程對(duì)象引用的請(qǐng)求。標(biāo)準(zhǔn)的 Sun Java SDK 提供了一個(gè)簡(jiǎn)單的 RMI 注冊(cè)表服務(wù)程序,即 rmiregistry 用于監(jiān)聽(tīng)特定的端口,等待遠(yuǎn)程對(duì)象的注冊(cè),以及客戶端對(duì)這些遠(yuǎn)程對(duì)象引用的檢索請(qǐng)求。

    在運(yùn)行我們的示例程序之前,首先要啟動(dòng) RMI 的注冊(cè)表服務(wù)。這個(gè)過(guò)程很簡(jiǎn)單,只要直接運(yùn)行 rmiregistry 命令即可。缺省的情況下,該服務(wù)將監(jiān)聽(tīng) 1099 端口。如果需要指定其它的監(jiān)聽(tīng)端口,可以在命令行指定希望監(jiān)聽(tīng)的端口(如果你指定了其它端口,需要修改示例程序以適應(yīng)環(huán)境)。如果希望該程序在后臺(tái)運(yùn)行,在 Unix 上可以以如下方式運(yùn)行(當(dāng)然,可以缺省端口參數(shù)):

    $ rmiregistry 1099 &

    在 Windows 操作系統(tǒng)中可以這樣運(yùn)行:

    C:\> start rmiregistry 1099

    我們的 rmitutorial.Bootstrap 類(lèi)將用于啟動(dòng)遠(yuǎn)程對(duì)象,并將其綁定在 RMI 注冊(cè)表中。運(yùn)行該類(lèi)后,遠(yuǎn)程對(duì)象也將進(jìn)入監(jiān)聽(tīng)狀態(tài),等待來(lái)自客戶端的方法調(diào)用請(qǐng)求。

    $ java rmitutorial.Bootstrap
    ComputeEngine exported
    ComputeEngine bound

    啟動(dòng)遠(yuǎn)程對(duì)象后,打開(kāi)另一個(gè)命令行窗口,運(yùn)行客戶端。命令行的第一個(gè)參數(shù)為 RMI 注冊(cè)表的地址,第二個(gè)參數(shù)為期望的 π 值精度。參考下面的示例:

    $ java rmitutorial.Client localhost 50
    3.14159265358979323846264338327950288419716939937511

    12. 其它信息

    在演示示例程序時(shí),我們實(shí)際上是在同一主機(jī)上運(yùn)行的服務(wù)器和客戶端,并且無(wú)論是服務(wù)器和客戶端所需的類(lèi)都在相同的類(lèi)路徑上,可以同時(shí)被服務(wù)器和客戶端所訪問(wèn)。這忽略了 Java RMI 的一個(gè)重要細(xì)節(jié),即動(dòng)態(tài)類(lèi)裝載。因?yàn)?RMI 的特性(包括其它幾個(gè)特性)并不適用于 J2EE 的 RMI-IIOP 和 EJB 技術(shù),所以,本文將不作詳細(xì)介紹,請(qǐng)讀者自行參考本文給出的參考資料。不過(guò),為了讓好奇的讀者不至于過(guò)分失望,這里簡(jiǎn)單介紹一下動(dòng)態(tài)類(lèi)裝載的基本思想。

    RMI 運(yùn)行時(shí)系統(tǒng)采用動(dòng)態(tài)類(lèi)裝載機(jī)制來(lái)裝載分布式應(yīng)用所需的類(lèi)。如果你可以直接訪問(wèn)應(yīng)用所涉及的所有包括服務(wù)器端客戶端在內(nèi)的主機(jī),并且可以把分布式應(yīng)用所需的所有類(lèi)都安裝在每個(gè)主機(jī)的 CLASSPATH 中(上面的示例就是極端情況,所有的東西都在本地主機(jī)),那么你完全不必關(guān)心 RMI 類(lèi)裝載的細(xì)節(jié)。顯然,既然是分布式應(yīng)用,情況往往正相反。對(duì)于 RMI 應(yīng)用,客戶端需要裝載客戶端自身所需的類(lèi),將要調(diào)用的遠(yuǎn)程對(duì)象的遠(yuǎn)程接口類(lèi)以及對(duì)應(yīng)的 Stub 類(lèi);服務(wù)器端則要裝載遠(yuǎn)程對(duì)象的實(shí)現(xiàn)類(lèi)以及對(duì)應(yīng)的 Skeleton 類(lèi)(Java 1.2 之后不需要 Skeleton 類(lèi))。RMI 在處理遠(yuǎn)程調(diào)用涉及的遠(yuǎn)程引用,參數(shù)以及返回值時(shí),可以將一個(gè)指定的 URL 編碼到流中。交互的另一端可以通過(guò) 該 URL 獲得處理這些對(duì)象所需的類(lèi)文件。這一點(diǎn)類(lèi)似于 Applet 中的 CODEBASE 的概念,交互的兩端通過(guò) HTTP 服務(wù)器發(fā)布各自控制的類(lèi),允許交互的另一端動(dòng)態(tài)下載這些類(lèi)。以我們的示例為例,客戶端不必部署 ComputeEngine_Stub 的類(lèi)文件,而可以通過(guò)服務(wù)器端的 HTTP 服務(wù)器獲得類(lèi)文件。同樣,服務(wù)器端也不需要客戶端實(shí)現(xiàn)的定制任務(wù) Pi 的類(lèi)文件。

    注意,這種動(dòng)態(tài)類(lèi)裝載將需要交互的兩端加載定制的安全管理器(參見(jiàn) java.rmi.RMISecurityManager API),以及對(duì)應(yīng)的策略文件。

    posted on 2006-09-27 23:38 凌宇 閱讀(264) 評(píng)論(0)  編輯  收藏

    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    常用鏈接

    留言簿(3)

    隨筆檔案(3)

    文章檔案(14)

    相冊(cè)

    收藏夾

    Java

    最新隨筆

    搜索

    •  

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 99久久婷婷国产综合亚洲| 夜夜春亚洲嫩草影院| 一色屋成人免费精品网站| 99在线观看视频免费| 一级毛片免费播放| 最近中文字幕无免费| 16女性下面无遮挡免费| 永久免费在线观看视频| ww在线观视频免费观看| 国产99视频精品免费观看7| 最近中文字幕完整版免费高清| 222www免费视频| 97免费人妻无码视频| 免费国产黄线在线观看| 好大好深好猛好爽视频免费| 免费高清在线影片一区| 国产人妖ts在线观看免费视频| 国产一区在线观看免费| 亚洲一区视频在线播放| 亚洲综合色婷婷七月丁香| 亚洲av无码不卡| 亚洲日本乱码一区二区在线二产线 | 99热亚洲色精品国产88| 亚洲国产精品自在自线观看| 无码天堂va亚洲va在线va| 一级做a毛片免费视频| 十八禁在线观看视频播放免费| 久久aa毛片免费播放嗯啊| 成人在线免费看片| 日本大片在线看黄a∨免费| 亚洲人成网站色在线入口| 久久亚洲国产视频| wwwxxx亚洲| 伊人久久国产免费观看视频| a毛片视频免费观看影院| 精品国产sm捆绑最大网免费站| 好大好深好猛好爽视频免费| 久久精品亚洲男人的天堂| 麻豆亚洲av熟女国产一区二| 亚洲色偷偷偷综合网| 国产三级在线免费观看|