RMI使用概述(ZZ)

  1. RMI 的用途是使分佈在不同虛擬機中的對象的外表和行爲都象本地對象一樣。調用遠程對象的虛擬機有時稱爲客戶機。類似地,我們將包含遠程對象的虛擬機稱爲服務器。
  2. 獲取遠程對象的引用和獲取本地對象的引用有點不同,但一旦獲得了引用,就可以象調用本地對象一樣調用遠程對象,如下面的代碼片段所示。RMI 基礎結構將自動截取請求,找到遠程對象,並遠程地分派請求。
  3. 這種位置透明性甚至包括垃圾收集。也就是說,客戶機不必特地釋放遠程對象,RMI 基礎結構和遠程虛擬機爲您處理垃圾收集。
  4. 爲了實現位置透明性,RMI 引入了兩種特殊類型的對象:存根(stub)和框架(skeleton)。
  5. 存根是代表遠程對象的客戶機端對象。存根具有和遠程對象相同的接口或方法列表,但當客戶機調用存根方法時,存根通過 RMI 基礎結構將請求轉發到遠程對象,實際上由遠程對象執行請求。
  6. 在服務器端,框架對象處理“遠方”的所有細節,因此實際的遠程對象不必擔心這些細節。也就是說,您完全可以象編碼本地對象一樣來編碼遠程對象。框架將遠程對象從 RMI 基礎結構分離開來。在遠程方法請求期間,RMI 基礎結構自動調用框架對象,因此它可以發揮自己的作用。
  7. 關於這種設置的最大的好處是,您不必親自爲存根和框架編寫代碼。JDK 包含工具 rmic,它會爲您創建存根和框架的類文件。
  8. 實際上,RMI 使用 TCP/IP 套接字來傳達遠程方法請求。儘管套接字是相當可靠的傳輸,但還是有許多事情可能出錯。例如,假設服務器計算機在方法請求期間崩潰了。或者,假設客戶機和服務器計算機是通過因特網連接的,而客戶機掉線了。
  9. 關鍵在於,使用遠程對象時比使用本地對象時有更多可能出錯的機會。因此客戶機程序能夠完美地從錯誤中恢復就很重要了。
  10. 因此要讓客戶機知道此類錯誤,每個將要被遠程調用的方法都必須拋出RemoteException,java.rmi 包中定義了該異常。 
  11. 讓我們研究一下編寫對象服務器所涉及的步驟。我們將花一些時間研究客戶機端。
  12. 您需要做的第一件事情是定義用於遠程對象的接口。這個接口定義了客戶機能夠遠程地調用的方法。遠程接口和本地接口的主要差異在於,遠程方法必須能拋出 RemoteException。
  13. 接下來,編寫一個實現該接口的類。然後,編寫在服務器上運行的主程序。這個程序必須實例化一個或多個服務器對象,然後,通常將遠程對象註冊到 RMI 名稱註冊表,這樣客戶機就能夠找到對象。
  14. 最後,生成存根和框架的代碼。JDK 提供了 rmic 工具,它讀取遠程對象的類文件併爲存根和框架創建類文件。
  15. 編寫遠程接口
  16. 下面的代碼顯示了一個簡單遠程接口的接口定義。實現這個接口的對象提供三個方法:一個方法返回字符串、一個方法接受字符串作爲參數、而另一個方法不接受參數也不返回任何結果。正如先前提到的,這些方法必須能拋出 RemoteException,如果客戶機和服務器之間的通信出錯,則客戶機將捕獲此異常。
  17. 注:該接口本身繼承了 java.rmi 包中定義的Remote接口。Remote接口本身沒有定義方法,但通過繼承它,我們說明該接口可以被遠程地調用。
  18. 這裏是編寫遠程接口的代碼:
  19. import java.rmi.*;
  20. public interface Meeting extends Remote
  21. {
  22.   public String getDate ()
  23.      throws RemoteException;
  24.   public void setDate ( String date )
  25.      throws RemoteException;
  26.   public void scheduleIt()
  27.      throws RemoteException;
  28. }
  29. 現在,讓我們研究一個實現遠程 Meeting接口的類。通常,MeetingServer繼承UnicastRemoteObject類,UnicastRemoteObject類提供遠程對象所需的基本行爲。術語“單播(unicast)”是指每個客戶機存根引用單個遠程對象的現象。以後,RMI 可能會允許“多播(multicasting)”,即一個存根可以引用幾個對等的遠程對象。使用多播,RMI 基礎結構可以均衡一組遠程對象之間的負載。
  30. 下面的代碼樣本顯示了兩個方法的實現:Meeting接口中定義的getDate方法和一個無參數構造器。請注意:這兩者都拋出RemoteException;所有由客戶機遠程調用的方法和構造器都需要拋出這個異常。在本示例中,構造器無實際工作可做,但我們還是需要定義它,這樣它能夠拋出遠程異常。
  31. 但是getDate方法很有趣。它向調用者返回一個字符串的引用。雖然這看起來可能很簡單,但是,在這裏 RMI 基礎結構和框架以及存根實際上有很多工作要做。它們必須協同工作,以便將字符串的一個副本傳回客戶機,然後,在客戶機的虛擬機中作爲對象重新創建。
  32. 這裏是實現遠程接口的代碼:
  33. import java.rmi.*;
  34. import java.rmi.server.*;
  35. public class MeetingServer extends UnicastRemoteObject implements Meeting
  36. {
  37.   private String ivDate = new String ( "1/1/2000" );
  38.   public MeetingServer() throws RemoteException
  39.   {
  40.   }
  41.   public String getDate()throws RemoteException
  42.   {
  43.     return ivDate;
  44.   }
  45. ...
  46. }
  47. 編寫 RMI 服務器概述
  48. 除了實現接口之外,我們還需要編寫服務器的主程序。目前 RMI 不支持作爲 applet 的服務器程序,所以主程序必需是獨立的 Java 應用程序。您要麼爲主程序編碼一個單獨的 Java 類,要麼象我們在這裏所做的一樣,只在實現類中編碼一個 main 方法。
  49. 還請注意,我們編碼的 main 函數可以向命令行拋出任何與 RMI 相關的異常。對於象本文中的小樣本程序,這樣做沒有問題,但在實際程序中,您可能要將執行的步驟包括到獨立的 try-catch 塊中,從而能夠更好地執行錯誤處理。
  50. 這裏是編寫 RMI 服務器的代碼結構:
  51. import java.rmi.*;
  52. import java.rmi.server.*;
  53. public class MeetingServer extends UnicastRemoteObject implements Meeting
  54. {
  55.   ...
  56.   public static void main (String [] args ) throws
  57.     RemoteException, java.net.MalformedURLException,
  58.     RMISecurityException
  59.   {
  60.     // 1. Set Security manager
  61.     // 2. Create an object instance
  62.     // 3. Register object into the name space
  63.   }
  64. }
  65. 服務器 main 通常要做的步驟是:
  66. 1.  安裝安全性管理器類,它允許服務器程序從其它機器接收存根類 
  67. 2.  創建服務器對象的實例 
  68. 3.  將服務器對象註冊到 RMI 命名註冊表以便客戶機程序能找到該服務器對象 
  69. 現在,讓我們進一步研究這些步驟。
  70. 設置安全性管理器
  71. 第一步是安裝 RMI 安全性管理器。儘管這不是嚴格必須的,但它確實允許服務器虛擬機下載類文件。例如,假設客戶機調用服務器中的方法,該方法接受對應用程序定義的對象類型(例如 BankAccount)的引用。通過設置安全性管理器,我們允許 RMI 運行時動態地將 BankAccount 類文件複製到服務器,從而簡化了服務器上的配置。
  72. 讓 RMI 動態地下載這些類的弊端是有安全性風險。也就是說,實質上我們是在讓服務器執行來自另一臺機器的代碼。雖然我們希望這些類文件不會危及服務器,但如果希望避免這樣的風險,則您的 RMI 服務器不應該安裝安全性管理器。然後,您必須確保將所有類文件安裝在本地服務器的類路徑中。這裏是用於設置安全性管理器的代碼:
  73. import java.rmi.*;
  74. import java.rmi.server.*;
  75. public static void main (String [] args ) throws
  76.     RemoteException, java.net.MalformedURLException,
  77.     RMISecurityException
  78. {
  79.     System.setSecurityManager (
  80.         new RMISecuritymanager() );
  81.         
  82.     MeetingServer ms = new MeetingServer();
  83.     Naming.rebind("rmi://myhost.com/Meeting", ms );
  84. }
  85. 注:傳遞對象參數類型是涉及許多方面的主題,因爲有兩種方法實現它。一種是在通信線路上僅傳遞引用;另一種是將對象序列化並在遠程創建新對象。在本教程中,我們不會更深入地討論這些問題,但您應該閱讀 JDK 中的 RMI 文檔以獲取更多詳細信息。
  86. 命名遠程對象  
  87. 服務器的下一步工作是創建服務器對象的初始實例,然後將對象的名稱寫到 RMI 命名註冊表。RMI 命名註冊表允許您將 URL 名稱分配給對象以便客戶機查找它們。要註冊名稱,需調用靜態 rebind 方法,它是在 Naming 類上定義的。這個方法接受對象的 URL 名稱以及對象引用。
  88. 名稱字符串是很有趣的部分。它包含 rmi:// 前綴、運行 RMI 對象的服務器的計算機主機名和對象本身的名稱,這個名稱正是您所想要的。注:您可以調用由 java.net.InetAddress 類定義的getLocalHost方法,而不必象我們在這裏所做的一樣硬編碼主機名。
  89. 這裏是命名遠程對象的代碼:
  90. import java.rmi.*;
  91. import java.rmi.server.*;
  92. public static void main (String [] args ) throws
  93.     RemoteException, java.net.MalformedURLException,
  94.     RMISecurityException
  95. {
  96.     System.setSecurityManager ( new RMISecuritymanager() );
  97.     MeetingServer ms = new MeetingServer();
  98.     Naming.rebind ("rmi://myhost.com/Meeting", ms );
  99. }
  100. 生成存根和框架
  101. 編寫並編譯了服務器實現之後,就準備創建存根和框架類。那很容易:只要運行 JDK 的 rmic 命令,指定實現類文件名(不帶擴展名)。rmic 工具將爲每個類文件創建一個存根和一個框架。然後,您需要正確地部署這些文件,在討論完編寫客戶機端代碼之後,我們將討論部署問題。
  102. 客戶機開發概述 
  103. 現在,讓我們研究客戶機端。首先,您必須確定是想編寫客戶機獨立應用程序還是客戶機 applet。應用程序的設置簡單些,但 applet 更容易部署,因爲 Java RMI 基礎結構能夠將它們下載到客戶機機器。我們將討論如何實現這兩者。
  104. 在客戶機中,代碼需要首先使用 RMI 註冊表來查找遠程對象。一旦這樣做了之後,客戶機就可以調用由遠程接口定義的方法。
  105. 應用程序 vs. applet
  106. 讓我們來簡略看一下 Java 應用程序和 applet 之間的差異。如果編寫應用程序,必須定義一個 main 入口點,可以從中執行 RMI 啓動代碼。然後,您必須在客戶機機器上安裝應用程序的類文件。如果有多臺客戶機計算機,則您必須在每臺機器上手工安裝這個應用程序的類文件。並且,正如我們過一會兒將要看到的,您還要在客戶機應用程序計算機上安裝一些與 RMI 相關的服務器文件。
  107. 相反,如果您決定編寫一個 applet,則不需要 main 入口點。applet 重寫了由瀏覽器調用的 init 方法。可以在 init 中編寫與您在應用程序的 main 中編寫的同類代碼。applet 的主要優點是,除了瀏覽器,您不必在客戶機計算機上預安裝任何東西 ― Java RMI 基礎結構將自動下載所有必要的類文件。但是,請注意,您必須編寫一個瀏覽器能夠裝入的 HTML 文件。我們將簡略地討論這一切。
  108. 編寫 RMI 客戶機應用程序
  109. 讓我們研究一下編寫 RMI 獨立應用程序所包含的步驟。我們照常將 main 方法編碼爲入口點,並且在其中執行 RMI 初始化步驟。首先設置安全性管理器,以便 RMI 運行時能夠下載類文件,然後通過使用 RMI 命名註冊表獲取一個對遠程對象的引用。最後我們就能夠調用遠程方法了。這裏是編寫 RMI 客戶機應用程序的代碼:
  110. import java.rmi.*;
  111. public class MeetingClient{
  112.   public static void main ( String [] args ) throws RemoteException,
  113.            java.net.MalformedURLException, java.rmi.NotBoundException
  114.   {
  115.     // 1. Set Security Manager
  116.     // 2. Look up remote object from name space
  117.     // 3. Call remote methods
  118.   }
  119. }
  120. 雖然這個代碼顯示了僅在Main方法中調用遠程方法,但一旦應用程序檢索到對象引用,則應用程序也可以在其它方法中調用遠程方法。此外,請注意,不需要進行清理,即使對遠程對象,Java RMI 基礎結構也會保證進行垃圾收集工作。
  121. 現在我們將更詳細地討論這些步驟。
  122. 設置安全性管理器    第 14 頁(共23 頁)
  123. 該代碼看起來和對象服務器main中的代碼類似。象對象服務器一樣,客戶機應用程序也可以選擇是否設置安全性管理器。並且原因也相似:RMI 運行時會自動將遠程對象的存根類文件下載到客戶機,但僅當應用程序安裝了安全性管理器時才能這樣做。如果應用程序使用缺省的安全性管理器,則需要在客戶機計算機的類路徑預安裝存根類文件,否則應用程序將捕獲到一個安全性異常。
  124. 還應該注意,在這個代碼中 main方法只是將與 RMI 相關的異常拋回到命令行。更健壯的應用程序應該包含 try-catch 塊以便本地進行錯誤處理。這裏是設置安全性管理器的代碼:
  125. import java.rmi.*;
  126. public static void main ( String [] args )throws RemoteException,java.net.MalformedURLException,java.rmi.NotBoundException
  127.   {
  128.     System.setSecurityManager(new RMISecurityManager());
  129.     //...  
  130.   }
  131. 查找對象
  132. 一旦應用程序安裝了可使用的安全性管理器,它就可以從 RMI 命名註冊表檢索遠程對象的引用。要這樣做,需調用靜態lookUp方法,傳遞對象服務器爲遠程對象所註冊的相同名稱。lookup 方法將檢索對遠程對象的引用並創建存根對象,這裏,存根對象存儲在變量r中。
  133. 如果客戶機提供的名稱與註冊表中的名稱不匹配,則lookUp方法拋NoBoundExceptionn。如果所提供的 URL 無效,則 lookUp拋出MalformedURLException。在下面的這個簡單的代碼片段中,沒有顯式地捕獲這些異常,但在實際程序中您需要這樣做。
  134. 請注意返回的引用類型是Remote類型,它是所有遠程對象的超類。但是實際上,我們想要的是作爲遠程接口中定義的Meeting接口的引用。因此,我們需要如下一頁所述那樣對引用進行向下強制類型轉換。
  135. 這裏是查找對象的代碼:
  136. import java.rmi.*;
  137. public static void main ( String [] args )  throws RemoteException,java.net.MalformedURLException,java.rmi.NotBoundException
  138.   {
  139.     System.setSecurityManager(new RMISecurityManager());
  140.     Remote r = Naming.lookup {"rmi://myhost.com/Meeting" );
  141.     ...
  142.   }
  143. 調用遠程方法
  144. 在應用程序能夠調用遠程方法之前,它必須轉換遠程引用的類型以匹配接口定義,在本示例中是Meeting。雖然您可以使用簡單的強制類型轉換做到這一點,但這裏顯示的代碼首先通過調用InstanseOf運算符來檢查強制類型轉換是否有效。當遠程對象不是預期的類型時,這種技術可使您可以避免InvalidCast運行時異常。
  145. 一旦我們成功地對引用進行了強制類型轉換,就可以調用遠程方法;這裏是 getDate 方法,它返回一個字符串。
  146. 這裏是調用遠程方法的代碼:
  147. import java.rmi.*;
  148. public static void main ( String [] args )throws RemoteException,
  149.            java.net.MalformedURLException, java.rmi.NotBoundException
  150.   {
  151.     //...
  152.     Remote r = Naming.lookup ("rmi://myhost.com/Meeting");
  153.     String s = null;
  154.     if (r instanceof Meeting )
  155.     {
  156.       ms = (Meeting)r;
  157.       s = ms.getDate();
  158.     }
  159.   }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章