RMI規(guī)范--第二章
Java 分布式對(duì)象模型
2.1 分布式對(duì)象應(yīng)用程序
RMI 應(yīng)用程序通常包括兩個(gè)獨(dú)立的程序:服務(wù)器程序和客戶機(jī)程序。典型的服務(wù)
器應(yīng)用程序?qū)?chuàng)建多個(gè)遠(yuǎn)程對(duì)象,使這些遠(yuǎn)程對(duì)象能夠被引用,然后等待客戶機(jī)
調(diào)用那些遠(yuǎn)程對(duì)象上的方法。而典型的客戶機(jī)程序則從服務(wù)器中得到一個(gè)或多個(gè)
遠(yuǎn)程對(duì)象的引用,然后調(diào)用遠(yuǎn)程對(duì)象的方法。RMI 為服務(wù)器和客戶機(jī)進(jìn)行通訊
和信息傳遞提供了一種機(jī)制。這樣的應(yīng)用程序有時(shí)被稱為分布式對(duì)象應(yīng)用程序。
分布式對(duì)象應(yīng)用程序需要:
定位遠(yuǎn)程對(duì)象
應(yīng)用程序可使用兩種機(jī)制中的一種得到對(duì)遠(yuǎn)程對(duì)象的引用。它既可用 RMI 的簡(jiǎn)
單命名工具 rmiregistry 來(lái)注冊(cè)它的遠(yuǎn)程對(duì)象;也可將遠(yuǎn)程對(duì)象引用作為常規(guī)
操作的一部分來(lái)進(jìn)行傳遞和返回。
與遠(yuǎn)程對(duì)象通訊
遠(yuǎn)程對(duì)象間通訊的細(xì)節(jié)由 RMI 處理;對(duì)于程序員來(lái)說(shuō),遠(yuǎn)程通訊看起來(lái)就象標(biāo)
準(zhǔn)的 Java 方法調(diào)用。給作為參數(shù)或返回值傳遞的對(duì)象加載類字節(jié)碼因?yàn)?RMI
允許調(diào)用程序?qū)⒓?Java 對(duì)象傳給遠(yuǎn)程對(duì)象,所以 RMI 將提供必要的機(jī)制,
既可以加載對(duì)象的代碼又可以傳輸對(duì)象的數(shù)據(jù)。
服務(wù)器調(diào)用注冊(cè)服務(wù)程序以使名字與遠(yuǎn)程對(duì)象相關(guān)聯(lián)。客戶機(jī)在服務(wù)器注冊(cè)服務(wù)
程序中用遠(yuǎn)程對(duì)象的名字查找該遠(yuǎn)程對(duì)象,然后調(diào)用它的方法。RMI 能用 Java
系統(tǒng)支持的任何 URL 協(xié)議(例如 HTTP、FTP、file 等)加載類字節(jié)碼。
2.2 術(shù)語(yǔ)的定義
在 Java 分布式對(duì)象模型中,remote object 是這樣一種對(duì)象:它的方法可以
從其它 Java 虛擬機(jī)(可能在不同的主機(jī)上)中調(diào)用。該類型的對(duì)象由一種或
多種 remote interfaces(它是聲明遠(yuǎn)程對(duì)象方法的 Java 接口)描述。
遠(yuǎn)程方法調(diào)用 (RMI) 就是調(diào)用遠(yuǎn)程對(duì)象上遠(yuǎn)程接口的方法的動(dòng)作。更為重要的
是,遠(yuǎn)程對(duì)象的方法調(diào)用與本地對(duì)象的方法調(diào)用語(yǔ)法相同。
2.3 分布式和非分布式模型的比較
Java 分布式對(duì)象模型在以下幾方面與 Java 對(duì)象模型相似:
遠(yuǎn)程對(duì)象的引用在任一種方法調(diào)用中(本地或遠(yuǎn)程)都能以參數(shù)形式傳遞或以結(jié)
果形式返回。
遠(yuǎn)程對(duì)象可以被強(qiáng)制轉(zhuǎn)換成任何遠(yuǎn)程界面,只要該界面為使用內(nèi)置 Java 語(yǔ)法
進(jìn)行強(qiáng)制類型轉(zhuǎn)換的實(shí)現(xiàn)所支持。
內(nèi)置 Java 操作符 instanceof 可用來(lái)測(cè)試遠(yuǎn)程對(duì)象所支持的遠(yuǎn)程接口。
Java 分布式對(duì)象模型在以下幾方面與 Java 對(duì)象模型不同:
遠(yuǎn)程對(duì)象的客戶機(jī)與遠(yuǎn)程接口發(fā)生交互,而從不與這些接口的實(shí)現(xiàn)類交互。
遠(yuǎn)程方法的非遠(yuǎn)程參數(shù)和返回結(jié)果是通過(guò)復(fù)制而非引用的方式傳遞的。這是因?yàn)?
對(duì)象的引用只在單個(gè)虛擬機(jī)中才有用。
遠(yuǎn)程對(duì)象以引用的方式進(jìn)行傳遞,而不是復(fù)制實(shí)際的遠(yuǎn)程實(shí)現(xiàn)。
某些 java.lang.Object 類定義的方法的語(yǔ)義專用于遠(yuǎn)程對(duì)象。
因?yàn)檎{(diào)用遠(yuǎn)程對(duì)象的失敗模式本來(lái)就比調(diào)用本地對(duì)象的失敗模式復(fù)雜,所以客戶
機(jī)必須處理遠(yuǎn)程方法調(diào)用期間發(fā)生的額外異常。
2.4 RMI 接口和類概述
2.4.1 java.rmi.Remote 接口
在 RMI 中,遠(yuǎn)程接口是聲明了可從遠(yuǎn)程 Java 虛擬機(jī)中調(diào)用的方法集。遠(yuǎn)程接
口必須滿足下列要求:
遠(yuǎn)程接口至少必須直接或間接擴(kuò)展 java.rmi.Remote 接口。
遠(yuǎn)程接口中的方法聲明必須滿足下列遠(yuǎn)程方法聲明的要求:
遠(yuǎn)程方法聲明在其 throws 子句中除了要包含與應(yīng)用程序有關(guān)的異常(注意與
應(yīng)用程序有關(guān)的異常無(wú)需擴(kuò)展 java.rmi.RemoteException )之外,還必須包
括 java.rmi.RemoteException 異常(或它的超類,例如
java.io.IOException 或 java.lang.Exception )。
遠(yuǎn)程方法聲明中,作為參數(shù)或返回值聲明的(在參數(shù)表中直接聲明或嵌入到參數(shù)
的非遠(yuǎn)程對(duì)象中)遠(yuǎn)程對(duì)象必須聲明為遠(yuǎn)程接口,而非該接口的實(shí)現(xiàn)類。
java.rmi.Remote 接口是一個(gè)不定義方法的標(biāo)記接口:
public interface Remote
遠(yuǎn)程接口必須至少擴(kuò)展 java.rmi.Remote 接口(或其它擴(kuò)展
java.rmi.Remote 的遠(yuǎn)程接口)。然而,遠(yuǎn)程接口在下列情況中可以擴(kuò)展非遠(yuǎn)
程接口:
遠(yuǎn)程接口也可擴(kuò)展其它非遠(yuǎn)程接口,只要被擴(kuò)展接口的所有方法(如果有)滿足
遠(yuǎn)程方法聲明的要求。
例如,下面的接口 BankAccount 即為訪問(wèn)銀行帳戶定義了一個(gè)遠(yuǎn)程接口。它包
含往帳戶存款、使帳戶收支平衡和從帳戶取款的遠(yuǎn)程方法:
public interface BankAccount extends java.rmi.Remote
{
public void deposit(float amount)
throws java.rmi.RemoteException;
public void withdraw(float amount)
throws OverdrawnException, java.rmi.RemoteException;
public float getBalance()
throws java.rmi.RemoteException;
}
下例說(shuō)明了有效的遠(yuǎn)程接口 Beta。它擴(kuò)展非遠(yuǎn)程接口 Alpha(有遠(yuǎn)程方法)和
接口 java.rmi.Remote:
public interface Alpha
{
public final String okay = "constants are okay too";
public Object foo(Object obj)
throws java.rmi.RemoteException;
public void bar() throws java.io.IOException;
public int baz() throws java.lang.Exception;
}
public interface Beta extends Alpha, java.rmi.Remote {
public void ping() throws java.rmi.RemoteException;
}
2.4.2 RemoteException 類
java.rmi.RemoteException 類是在遠(yuǎn)程方法調(diào)用期間由 RMI 運(yùn)行時(shí)所拋出
的異常的超類。為確保使用 RMI 系統(tǒng)的應(yīng)用程序的健壯性,遠(yuǎn)程接口中聲明的
遠(yuǎn)程方法在其 throws 子句中必須指定 java.rmi.RemoteException(或它的
超類,例如 java.io.IOException 或 java.lang.Exception)。
當(dāng)遠(yuǎn)程方法調(diào)用由于某種原因失敗時(shí),將拋出 java.rmi.RemoteException 異
常。遠(yuǎn)程方法調(diào)用失敗的原因包括:
通訊失敗(遠(yuǎn)程服務(wù)器不可達(dá)或拒絕連接;連接被服務(wù)器關(guān)閉等。)
參數(shù)或返回值傳輸或讀取時(shí)失敗
協(xié)議錯(cuò)誤
RemoteException 類是一個(gè)已檢驗(yàn)的異常(必須由遠(yuǎn)程方法的調(diào)用程序處理并
經(jīng)編譯器檢驗(yàn)的異常),而不是 RuntimeException。
2.4.3 RemoteObject 類及其子類
RMI 服務(wù)器函數(shù)由 java.rmi.server.RemoteObject 及其子類
java.rmi.server.RemoteServer、java.rmi.server.UnicastRemoteObject
和 java.rmi.activation.Activatable 提供。
java.rmi.server.RemoteObject 為對(duì)遠(yuǎn)程對(duì)象敏感的 java.lang.Object
方法、hashCode、 equals 和 toString 提供實(shí)現(xiàn)。
創(chuàng)建遠(yuǎn)程對(duì)象并將其導(dǎo)出(使它們可為遠(yuǎn)程客戶機(jī)利用)所需的方法由類
UnicastRemoteObject 和 Activatable 提供。子類可以識(shí)別遠(yuǎn)程引用的語(yǔ)義,
例如服務(wù)器是簡(jiǎn)單的遠(yuǎn)程對(duì)象還是可激活的遠(yuǎn)程對(duì)象(調(diào)用時(shí)將執(zhí)行的遠(yuǎn)程對(duì)象)。
java.rmi.server.UnicastRemoteObject 類定義了單體(單路傳送)遠(yuǎn)程對(duì)
象,其引用只有在服務(wù)器進(jìn)程活著時(shí)才有效。
類 java.rmi.activation.Activatable 是抽象類,它定義的 activatable
遠(yuǎn)程對(duì)象在其遠(yuǎn)程方法被調(diào)用時(shí)開始執(zhí)行并在必要時(shí)自己關(guān)閉。
2.5 實(shí)現(xiàn)遠(yuǎn)程接口
實(shí)現(xiàn)遠(yuǎn)程接口的類的一般規(guī)則如下:
該類通常擴(kuò)展 java.rmi.server.UnicastRemoteObject,因而將繼承類
java.rmi.server.RemoteObject 和java.rmi.server.RemoteServer 提供
的遠(yuǎn)程行為。
該類能實(shí)現(xiàn)任意多的遠(yuǎn)程接口。
該類能擴(kuò)展其它遠(yuǎn)程實(shí)現(xiàn)類。
該類能定義遠(yuǎn)程接口中不出現(xiàn)的方法,但這些方法只能在本地使用而不能在遠(yuǎn)程
使用。
例如,下面的類 BankAcctImpl 實(shí)現(xiàn) BankAccount 遠(yuǎn)程接口并擴(kuò)展
java.rmi.server.UnicastRemoteObject 類:
package mypackage;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class BankAccountImpl extends UnicastRemoteObject implements
BankAccount
{
private float balance = 0.0;
public BankAccountImpl(float initialBalance)
throws RemoteException
{
balance = initialBalance;
}
public void deposit(float amount) throws RemoteException
{
...
}
public void withdraw(float amount) throws OverdrawnException,
RemoteException
{
...
}
public float getBalance() throws RemoteException
{
...
}
}
注意:必要時(shí),實(shí)現(xiàn)遠(yuǎn)程接口的類能擴(kuò)展除
java.rmi.server.UnicastRemoteObject 類以外的其它一些類。但實(shí)現(xiàn)類此
時(shí)必須承擔(dān)起一定的責(zé)任,即導(dǎo)出對(duì)象(由 UnicastRemoteObject 構(gòu)造函數(shù)
負(fù)責(zé))和實(shí)現(xiàn)從 java.lang.Object 類繼承的 hashCode、 equals 和
toString 方法的正確遠(yuǎn)程語(yǔ)義(如果需要)。
2.6 遠(yuǎn)程方法調(diào)用中的參數(shù)傳遞
傳給遠(yuǎn)程對(duì)象的參數(shù)或源于它的返回值可以是任意可序列化的 Java 對(duì)象。這包
括 Java 基本類型, 遠(yuǎn)程?Java 對(duì)象和實(shí)現(xiàn) java.io.Serializable 接口的
非遠(yuǎn)程 Java 對(duì)象。有關(guān)如何使類序列化的詳細(xì)信息,參見(jiàn) Java“對(duì)象序列化
規(guī)范”。本地得不到的作為參數(shù)或返回值的類,可通過(guò) RMI 系統(tǒng)進(jìn)行動(dòng)態(tài)下載。
有關(guān) RMI 讀取參數(shù)、返回值和異常時(shí)如何下載參數(shù)和返回值類的詳細(xì)信息,參
見(jiàn)“動(dòng)態(tài)類加載”(3.4)一節(jié)。
2.6.1 傳遞非遠(yuǎn)程對(duì)象
非遠(yuǎn)程對(duì)象將作為遠(yuǎn)程方法調(diào)用的參數(shù)傳遞或作為遠(yuǎn)程方法調(diào)用的結(jié)果返回時(shí),
是通過(guò)復(fù)制傳遞的;也就是使用 Java 對(duì)象序列化機(jī)制將該對(duì)象序列化。
因此,在遠(yuǎn)程對(duì)象調(diào)用過(guò)程中,當(dāng)非遠(yuǎn)程對(duì)象作為參數(shù)或返回值傳遞時(shí),非遠(yuǎn)程
對(duì)象的內(nèi)容在調(diào)用遠(yuǎn)程對(duì)象之前將被復(fù)制。
從遠(yuǎn)程方法調(diào)用返回非遠(yuǎn)程對(duì)象時(shí),將在調(diào)用的虛擬機(jī)中創(chuàng)建新對(duì)象。
2.6.2 傳遞遠(yuǎn)程對(duì)象
當(dāng)將遠(yuǎn)程對(duì)象作為遠(yuǎn)程方法調(diào)用的參數(shù)或返回值傳遞時(shí),遠(yuǎn)程對(duì)象的 stub 程序
即被傳遞出去。作為參數(shù)傳遞的遠(yuǎn)程對(duì)象僅能實(shí)現(xiàn)遠(yuǎn)程接口。
2.6.3 引用的完整性
如果一個(gè)對(duì)象的兩個(gè)引用在單個(gè)遠(yuǎn)程方法調(diào)用中以參數(shù)形式(或返回值形式)從
一個(gè)虛擬機(jī)傳到另一個(gè)虛擬機(jī)中,并且它們?cè)诎l(fā)送虛擬機(jī)中指向同一對(duì)象,則兩
個(gè)引用在接收虛擬機(jī)中將指向該對(duì)象的同一副本。進(jìn)一步說(shuō)就是:在單個(gè)遠(yuǎn)程方
法調(diào)用中,RMI 系統(tǒng)將在作為調(diào)用參數(shù)或返回值傳遞的對(duì)象中保持引用的完整性
。
2.6.4 類注解
當(dāng)對(duì)象在遠(yuǎn)程調(diào)用中被從一個(gè)虛擬機(jī)發(fā)送到另一個(gè)虛擬機(jī)中時(shí),RMI 系統(tǒng)在調(diào)用
流中用類的信息 (URL) 給類描述符加注解,以便該類能在接收器上加載。在遠(yuǎn)
程方法調(diào)用期間,調(diào)用可隨時(shí)下載類。
2.6.5 參數(shù)傳輸
為將 RMI 調(diào)用的參數(shù)序列化到遠(yuǎn)程調(diào)用的目的文件里,需要將該參數(shù)寫入作為
java.io.ObjectOutputStream 類的子類的流中。ObjectOutputStream 子類
將覆蓋 replaceObject 方法,目的是用其相應(yīng)的 stub 類取代每個(gè)遠(yuǎn)程對(duì)象。
對(duì)象參數(shù)將通過(guò) ObjectOutputStream 的 writeObject 方法寫入流中。而
ObjectOutputStream 則通過(guò) writeObject 方法為每個(gè)寫入流中的對(duì)象(包
含所寫對(duì)象所引用的對(duì)象)調(diào)用 replaceObject 方法。RMIObjectOutputStream
子類的 replaceObject 方法返回下列值:
如果傳給 replaceObject 的對(duì)象是 java.rmi.Remote 的實(shí)例,則返回遠(yuǎn)程對(duì)
象的 stub 程序。遠(yuǎn)程對(duì)象的 stub 程序通過(guò)對(duì)
java.rmi.server.RemoteObject.toStub
方法的調(diào)用而獲得。
如果傳給 replaceObject 的對(duì)象不是 java.rmi.Remote 的實(shí)例,則只返回
該對(duì)象。
RMI 的 ObjectOutputStream 子類也實(shí)現(xiàn) annotateClass 方法,該方法用類
的位置注解調(diào)用流以便能在接收器中下載該類。有關(guān)如何使用 annotateClass
的詳細(xì)信息,參見(jiàn)“動(dòng)態(tài)類加載”一節(jié)。
因?yàn)閰?shù)只寫入一個(gè) ObjectOutputStream,所以指向調(diào)用程序同一對(duì)象的引用
將在接收器那里指向該對(duì)象的同一副本。在接收器上,參數(shù)將被單個(gè)
ObjectInputStream 所讀取。
用于寫對(duì)象的 ObjectOutputStream(類似的還有用于讀對(duì)象的
ObjectInputStream )的所有其它缺省行為將保留在參數(shù)傳遞中。例如,寫對(duì)
象時(shí)對(duì) writeReplace 的調(diào)用及讀對(duì)象時(shí)對(duì) readResolve 的調(diào)用就是由 RMI
的參數(shù)編組與解編流完成的。
與上述 RMI 參數(shù)傳遞方式類似,返回值(或異常)將被寫入
ObjectOutputStream
的子類并和參數(shù)傳輸?shù)奶娲袨橄嗤?
2.7 定位遠(yuǎn)程對(duì)象
我們專門提供了一種簡(jiǎn)單的引導(dǎo)名字服務(wù)器,用于存儲(chǔ)對(duì)遠(yuǎn)程對(duì)象的已命名引用
。使用類 java.rmi.Naming 的基于 URL 的方法可以存儲(chǔ)遠(yuǎn)程對(duì)象引用。
客戶機(jī)要調(diào)用遠(yuǎn)程對(duì)象的方法,則必須首先得到該對(duì)象的引用。對(duì)遠(yuǎn)程對(duì)象的引
用通常是在方法調(diào)用中以返回值的形式取得。RMI 系統(tǒng)提供一種簡(jiǎn)單的引導(dǎo)名字
服務(wù)器,通過(guò)它得到給定主機(jī)上的遠(yuǎn)程對(duì)象。java.rmi.Naming 類提供基于統(tǒng)
一資源定位符 (URL) 的方法,用來(lái)綁定、再綁定、解開和列出位于某一主機(jī)及
端口上的名字-對(duì)象對(duì)。