來源 :
http://www.linuxuser.com.tw/skill_detail.php?cid=1108
EJB技術的基礎是另外兩種技術:RMI-IIOP和JNDI。要想瞭解EJB,一定要先瞭解RMI-IIOP和JNDI。因此,我們在介紹EJB細節之前,先瞭解這兩項技術。我們的介紹比較基本,因此大多數組織只要瞭解這些就已經夠了。
Java RMI-IIOP
Java RMI-IIOP(Java Remote Method Invocation over the Internet Inter-ORB Protocol)是J2EE的網路機制。Java RMI-IIOP允許你編寫分散式物件,使得物件的通信範圍能夠在記憶體中,跨Java虛擬機,跨物理設備。
Remote Method Invocation
RPC(remote procedure call)是一台機器的進程調用另一台機器的進程的過程。而remote method invocation則比RPC的概念更進一步,允許分散式物件間的通信。RMI-IIOP允許調用遠端物件的方法,而不僅僅是過程。這有利於面向物件編程。Java RMI (Remote Method Invocation 遠端方法調用)是用Java在JDK1.1中實現的,它大大增強了Java開發分散式應用的能力。Java作為一種風靡一時的網路開發語言,其巨大的威力就體現在它強大的開發分散式網路應用的能力上,而RMI就是開發百分之百純Java的網路分散式應用系統的核心解決方案之一。其實它可以被看作是RPC的Java版本。但是傳統RPC並不能很好地應用於分散式物件系統。而Java RMI 則支援存儲於不同位址空間的程式級物件之間彼此進行通信,實現遠端物件之間的無縫遠端調用。
remote method invocation決不簡單,需要考慮幾個問題:
marshalling和unmarshalling.在不同機器間通過網路傳遞變數(包括Java基本類型和物件),如果目的機器表示資料的方式和原機器不同該怎麼辦?例如二進位庫不同。因此marshalling和unmarshalling就是傳遞變數的過程。
變數傳遞方法.變數有兩種傳遞方法:pass-by-value和pass-by-reference。對於前者,你的目標方法只需使用一份copy,但對於後者,遠端方法對變數的任何修改都會影響到源資料。
網路和機器的不穩定.需要有一種機制保證一個JVM崩潰之後,不會影響系統的正常運作。
在 Java 分散式物件模型中,remote object 是這樣一種物件:它的方法可以從其他 Java 虛擬機(可能在不同的主機上)中調用。該類型的物件由一種或多種 remote interfaces(它是聲明遠端物件方法的 Java 介面)描述。遠端方法調用 (RMI) 就是調用遠端物件上遠端介面的方法的動作。更為重要的是,遠端物件的方法調用與本地物件的方法調用語法相同。
Remote Interface
RMI-IIOP遵循了介面和實現的原則。你寫的所有網路代碼都是應用於介面,而不是實現。實際上,你必須使用RMI-IIOP中的範例,沒有其他的選擇。直接在你的物件實現上執行遠端調用是不可能的,你只能在物件類的介面上單獨進行這一操作。
所以我們在使用RMI-IIOP時,你必須建立一個客戶介面,叫做remote interface。這個遠端介面應該擴展java.rmi.Remote介面。
Remote Object Implementation
遠端物件和客戶機的物理位置並不是很重要。可以運行在同一位址空間或是跨Internet運行。
為了使物件成為一個遠端物件,你需要執行一下步驟:
繼承javax.rmi.PortableRemoteObject。PortableRemoteObject是進行遠端調用的基類,當你的遠端物件調用構造器時,PortableRemoteObject物件的構造器也會自動被調用。
不繼承javax.rmi.PortableRemoteObject。如果你的遠端物件需要繼承其他的類,而Java不允許多重繼承,因此你不能繼承PortableRemoteObject。這時,你需要手動調用javax.rmi.PortableRemoteObject.exportObject()。
Stub和Skeletons
我們來看看在RMI-IIOP背後隱藏的網路架構。RMI-IIOP的一個好處就是你可以不用管你要調用的物件是本地的還是遠端的。這就叫做local/remote transparency。
RMI應用程式通常包括兩個獨立的程式:伺服器程式和客戶機程式。典型的伺服器應用程式將創建多個遠端物件,使這些遠端物件能夠被引用,然後等待客戶機調用這些遠端物件的方法。而典型的客戶機程式則從伺服器中得到一個或多個遠端物件的引用,然後調用遠端物件的方法。RMI為伺服器和客戶機進行通信和資訊傳遞提供了一種機制。
在與遠端物件的通信過程中,RMI使用標準機制:stub和skeleton。遠端物件的stub擔當遠端物件的客戶本地代表或代理人角色。調用程式將調用本地stub的方法,而本地stub將負責執行對遠端物件的方法調用。在RMI中,遠端物件的stub與該遠端物件所實現的遠端介面集相同。調用stub的方法時將執行下列操作:(1) 初始化與包含遠端物件的遠端虛擬機的連接;(2) 對遠端虛擬機的參數進行編組(寫入並傳輸);(3) 等待方法調用結果;(4) 解編(讀取)返回值或返回的異常;(5) 將值返回給調用程式。為了向調用程式展示比較簡單的調用機制,stub將參數的序列化和網路級通信等細節隱藏了起來。在遠端虛擬機中,每個遠端物件都可以有相應的skeleton(在JDK1.2環境中無需使用skeleton)。Skeleton負責將調用分配給實際的遠端物件實現。它在接收方法調用時執行下列操作:(1) 解編(讀取)遠程方法的參數;(2) 調用實際遠端物件實現上的方法;(3) 將結果(返回值或異常)編組(寫入並傳輸)給調用程式。stub和skeleton由rmic編譯器生成。
要實現local/remote transparency可沒有那麼簡單。為了遮罩你調用的是遠端主機上的物件,RMI-IIOP需要類比一個本地物件供你調用。這個本地物件叫做stub。它負責接受本地的方法調用請求,把這些請求委託給真正實現它們的物件(可以通過網路定位)。這樣就使得遠端調用看起來就和本地調用一樣。
利用RMI編寫分散式物件應用程式需要完成以下工作:(1) 定位遠端對象。應用程式可使用兩種機制中的一種得到對遠端物件的引用。它既可用RMI的簡單命名工具rmiregistry來註冊它的遠端物件,也可以將遠端物件引用作為常規操作的一部分來進行傳遞和返回。(2)與遠端物件通信。遠端物件間通信的細節由RMI處理,對於程式師來說,遠端通信看起來就像標準的Java方法調用。(3)給作為參數或返回值傳遞的物件載入類位元組碼。因為RMI允許調用程式將純Java物件傳給遠端物件,所以,RMI將提供必要的機制,既可以載入物件的代碼又可以傳輸物件的資料。在RMI分散式應用程式運行時,伺服器調用註冊服務程式以使名字與遠端對象相關聯。客戶機在伺服器上的註冊服務程式中用遠端物件的名字查找該遠端物件,然後調用它的方法。
定位遠端對象。應用程式可使用兩種機制中的一種得到對遠端物件的引用。它既可用 RMI 的簡單命名工具 rmiregistry 來註冊它的遠端物件;也可將遠端物件引用作為常規操作的一部分來進行傳遞和返回。
與遠端物件通訊。遠端物件間通訊的細節由 RMI 處理;對於程式師來說,遠端通訊看起來就象標準的 Java 方法調用。給作為參數或返回值傳遞的物件載入類位元組碼因為 RMI允許調用程式將純 Java 物件傳給遠端物件,所以 RMI 將提供必要的機制,既可以載入物件的代碼又可以傳輸物件的資料。伺服器調用註冊服務程式以使名字與遠端對象相關聯。客戶機在伺服器註冊服務程式中用遠端物件的名字查找該遠端物件,然後調用它的方法。RMI 能用 Java系統支援的任何 URL 協定(例如 HTTP、FTP、file 等)載入類位元組碼。
stub只是解決了一半的問題。我們還希望遠端物件也不用考慮網路問題。因此遠端物件也需要一個本地的skeleton來接受調用。skeleton接受網路調用並把調用委託給遠端物件實現。
你的J2EE伺服器應當提供一種方法來產生必須的stub和skeleton,以減輕你的對網路問題考慮的負擔。典型的是通過命令行工具來完成,例如sun的J2EE參考實現包就使用了一個名為rmic(RMI compiler)的工具來產生stub和skeleton類。你應當把stub部署在客戶機上,並把skeleton部署在伺服器上。
物件序列化和變數傳遞
在RMI分散式應用系統中,伺服器與客戶機之間傳遞的Java物件必須是可序列化的物件。不可序列化的物件不能在物件流中進行傳遞。物件序列化擴展了核心Java輸入/輸出類,同時也支援物件。物件序列化支援把物件編碼以及將通過它們可訪問到的物件編碼變成位元組流;同時,它也支援流中物件圖形的互補重構造。序列化用於輕型持久性和借助於套接字或遠端方法調用(RMI)進行的通信。序列化中現在包括一個 API(Application Programming Interface,應用程式介面),允許獨立於類的域指定物件的序列化資料,並允許使用現有協定將序列化資料欄寫入流中或從流中讀取,以確保與缺省讀寫機制的相容性。
為編寫應用程式,除多數瞬態應用程式外,都必須具備存儲和檢索 Java物件的能力。以序列化方式存儲和檢索物件的關鍵在於提供重新構造該物件所需的足夠物件狀態。存儲到流的物件可能會支援 Serializable(可序列化)或 Externalizable(可外部化)介面。對於Java物件,序列化形式必須能標識和校驗存儲其內容的物件所屬的 Java類,並且將該內容還原為新的實例。對於可序列化物件,流將提供足夠的資訊將流的域還原為類的相容版本。對於可外部化物件,類將全權負責其內容的外部格式。序列化 Java 物件的目的是:提供一種簡單但可擴充的機制,以序列化方式維護 Java物件的類型及安全屬性;具有支持編組和解編的擴展能力以滿足遠端物件的需要;具有可擴展性以支援 Java 物件的簡單持久性;只有在自定義時,才需對每個類提供序列化自實現;允許物件定義其外部格式。
java.rmi.Remote 介面
在 RMI 中,遠端介面是聲明了可從遠端 Java 虛擬機中調用的方法集。遠程接
口必須滿足下列要求:
遠端介面至少必須直接或間接擴展 java.rmi.Remote 介面。
遠端介面中的方法聲明必須滿足下列遠端方法聲明的要求:
遠端方法聲明在其 throws 子句中除了要包含與應用程式有關的異常(注意與應用程式有關的異常無需擴展 java.rmi.RemoteException )之外,還必須包括 java.rmi.RemoteException 異常(或它的超類,例如java.io.IOException 或 java.lang.Exception )。
遠端方法聲明中,作為參數或返回值聲明的(在參數表中直接聲明或嵌入到參數的非遠端物件中)遠端物件必須聲明為遠端介面,而非該介面的實現類。
java.rmi.Remote 介面是一個不定義方法的標記介面:
public interface Remote
遠端介面必須至少擴展 java.rmi.Remote 介面(或其他擴展java.rmi.Remote 的遠端介面)。然而,遠端介面在下列情況中可以擴展非遠端介面:
遠端介面也可擴展其他非遠端介面,只要被擴展介面的所有方法(如果有)滿足遠端方法聲明的要求。
例如,下面的介面 BankAccount 即為訪問銀行帳戶定義了一個遠端介面。它包含往帳戶存款、使帳戶收支平衡和從帳戶取款的遠程方法:
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;
}
下例說明了有效的遠端介面 Beta。它擴展非遠端介面 Alpha(有遠端方法)和介面 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;
}
RemoteException 類
java.rmi.RemoteException 類是在遠端方法調用期間由 RMI 運行時所拋出的異常的超類。為確保使用 RMI 系統的應用程式的健壯性,遠端介面中聲明的遠端方法在其 throws 子句中必須指定 java.rmi.RemoteException(或它的超類,例如 java.io.IOException 或 java.lang.Exception)。
當遠端方法調用由於某種原因失敗時,將拋出 java.rmi.RemoteException 異常。遠端方法調用失敗的原因包括:
通訊失敗(遠端伺服器不可達或拒絕連接;連接被伺服器關閉等。)
參數或返回值傳輸或讀取時失敗
協定錯誤
RemoteException 類是一個已檢驗的異常(必須由遠端方法的調用程式處理並經編譯器檢驗的異常),而不是 RuntimeException。
RemoteObject 類及其子類
RMI 伺服器函數由 java.rmi.server.RemoteObject 及其子類java.rmi.server.RemoteServer、java.rmi.server.UnicastRemoteObject和 java.rmi.activation.Activatable 提供。
java.rmi.server.RemoteObject 為對遠端對象敏感的 java.lang.Object方法、hashCode、 equals 和 toString 提供實現。
創建遠端物件並將其導出(使它們可為遠端客戶機利用)所需的方法由類UnicastRemoteObject 和 Activatable 提供。子類可以識別遠端引用的語義,例如伺服器是簡單的遠端物件還是可啟動的遠端物件(調用時將執行的遠端物件)。java.rmi.server.UnicastRemoteObject 類定義了單體(單路傳送)遠端對
象,其引用只有在伺服器進程活著時才有效。類 java.rmi.activation.Activatable 是抽象類,它定義的 activatable遠端物件在其遠端方法被調用時開始執行並在必要時自己關閉。
實現遠端介面
實現遠端介面的類的一般規則如下:
該類通常擴展 java.rmi.server.UnicastRemoteObject,因而將繼承類java.rmi.server.RemoteObject 和java.rmi.server.RemoteServer 提供的遠端行為。
該類能實現任意多的遠端介面。
該類能擴展其他遠端實現類。
該類能定義遠端介面中不出現的方法,但這些方法只能在本地使用而不能在遠端使用。
例如,下麵的類 BankAcctImpl 實現 BankAccount 遠端介面並擴展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
{
...
}
}
注意:必要時,實現遠端介面的類能擴展除java.rmi.server.UnicastRemoteObject 類以外的其他一些類。但實現類此時必須承擔起一定的責任,即導出物件(由 UnicastRemoteObject 構造函數負責)和實現從 java.lang.Object 類繼承的 hashCode、 equals 和toString 方法的正確遠端語義(如果需要)。
遠端方法調用中的參數傳遞
傳給遠端物件的參數或源於它的返回值可以是任意可序列化的 Java 物件。這包括 Java 基本類型, 遠端?Java 物件和實現 java.io.Serializable 介面的非遠端 Java 物件。有關如何使類序列化的詳細資訊,參見 Java“物件序列化規範”。本地得不到的作為參數或返回值的類,可通過 RMI 系統進行動態下載。
傳遞非遠端物件
非遠端物件將作為遠端方法調用的參數傳遞或作為遠端方法調用的結果返回時,是通過複製傳遞的;也就是使用 Java 物件序列化機制將該物件序列化。因此,在遠端物件調用過程中,當非遠端物件作為參數或返回值傳遞時,非遠端物件的內容在調用遠端物件之前將被複製。從遠端方法調用返回非遠端物件時,將在調用的虛擬機中創建新物件。
傳遞遠端物件
當將遠端物件作為遠端方法調用的參數或返回值傳遞時,遠端物件的 stub 程式即被傳遞出去。作為參數傳遞的遠端物件僅能實現遠端介面。
引用的完整性
如果一個物件的兩個引用在單個遠端方法調用中以參數形式(或返回值形式)從一個虛擬機傳到另一個虛擬機中,並且它們在發送虛擬機中指向同一物件,則兩個引用在接收虛擬機中將指向該對象的同一副本。進一步說就是:在單個遠端方法調用中,RMI 系統將在作為調用參數或返回值傳遞的對象中保持引用的完整性。
類注解
當物件在遠端調用中被從一個虛擬機發送到另一個虛擬機中時,RMI 系統在調用流中用類的資訊 (URL) 給類描述符加注解,以便該類能在接收器上載入。在遠程方法調用期間,調用可隨時下載類。
參數傳輸
為將 RMI 調用的參數序列化到遠端調用的目的檔裏,需要將該參數寫入作為java.io.ObjectOutputStream 類的子類的流中。ObjectOutputStream 子類將覆蓋 replaceObject 方法,目的是用其相應的 stub 類取代每個遠端物件。物件參數將通過 ObjectOutputStream 的 writeObject 方法寫入流中。而ObjectOutputStream 則通過 writeObject 方法為每個寫入流中的物件(包含所寫物件所引用的物件)調用 replaceObject 方法。RMIObjectOutputStream子類的 replaceObject 方法返回下列值:
如果傳給 replaceObject 的物件是 java.rmi.Remote 的實例,則返回遠端物件的 stub 程式。遠端物件的 stub 程式通過對java.rmi.server.RemoteObject.toStub方法的調用而獲得。如果傳給 replaceObject 的對象不是 java.rmi.Remote 的實例,則只返回該物件。
RMI 的 ObjectOutputStream 子類也實現 annotateClass 方法,該方法用類的位置注解調用流以便能在接收器中下載該類。有關如何使用 annotateClass的詳細資訊,參見“動態類載入”一節。因為參數只寫入一個 ObjectOutputStream,所以指向調用程式同一物件的引用將在接收器那裏指向該對象的同一副本。在接收器上,參數將被單個ObjectInputStream 所讀取。
用於寫物件的 ObjectOutputStream(類似的還有用於讀物件的ObjectInputStream )的所有其他缺省行為將保留在參數傳遞中。例如,寫物件時對 writeReplace 的調用及讀物件時對 readResolve 的調用就是由 RMI的參數編組與解編流完成的。
與上述 RMI 參數傳遞方式類似,返回值(或異常)將被寫入ObjectOutputStream的子類並和參數傳輸的替代行為相同。
定位遠端對象
我們專門提供了一種簡單的引導名字伺服器,用於存儲對遠端物件的已命名引用。使用類 java.rmi.Naming 的基於 URL 的方法可以存儲遠端物件引用。客戶機要調用遠端物件的方法,則必須首先得到該物件的引用。對遠端物件的引用通常是在方法調用中以返回值的形式取得。RMI 系統提供一種簡單的引導名字伺服器,通過它得到給定主機上的遠端物件。java.rmi.Naming 類提供基於統一資源定位符 (URL) 的方法,用來綁定、再綁定、解開和列出位於某一主機及埠上的名字-對象對。

