Java遠程方法調用

概述

  Java Remote Method Invocation ( RMI -- Java遠程方法調用)允許您使用Java編寫分佈式對象。本文將介紹RMI的優點以及如何將其連接到現有的和原有的系統中,以及與用Java 編寫的組件的連接。

  RMI爲採用Java對象的分佈式計算提供了簡單而直接的途徑。這些對象可以是新的Java對象,也可以是圍繞現有API的簡單的Java包裝程序。Java體現了“編寫一次就能在任何地方運行的模式。而RMI可將Java模式進行擴展,使之可在任何地方運行”。

  因爲RMI是以Java爲核心的,所以,它將Java的安全性和可移植性等強大功能帶給了分佈式計算。您可將代理和梢務邏輯等屬性移動到網絡中最合適的地方。如果您要擴展Java在系統中的使用,RMI將使您充分利用其強大功能。

  RMI可利用標準Java本機方法接口JNI與現有的和原有的系統相連接。RMI還可利用標準JDBC包與現有的關係數據庫連接。RMI/JNI和 RMI/JDBC相結合,可幫助您利用RMI與目前使用非Java語言的現有服務器進行通信,而且在您需要時可擴展Java在這些服務器上的使用。RMI 可幫助您在擴展使用時充分利用Java的強大功能。

優點

  從最基本的角度看,RMI是Java的遠程過程調用(RPC)機制。與傳統的RPC系統相比,RMI具有若干優點,因爲它是Java面向對象方法的一部分。傳統的RPC系統採用中性語言,所以是最普通的系統--它們不能提供所有可能的目標平臺所具有的功能。

  RMI以Java爲核心,可與採用本機方法與現有系統相連接。這就是說,RMI可採用自然、直接和功能全面的方式爲您提供分佈式計算技術,而這種技術可幫助您以不斷遞增和無縫的方式爲整個系統添加Java功能。

RMI的主要優點如下:

 面向對象:RMI可將完整的對象作爲參數和返回值進行傳遞,而不僅僅是預定義的數據類型。也就是說,您可以將類似Java哈希表這樣的複雜類型作爲一個參數進行傳遞。而在目前的RPC系統中,您只能依靠客戶機將此類對象分解成基本數據類型,然後傳遞這些數據類型,最後在服務器端重新創建哈希表。RMI則不需額外的客戶程序代碼(將對象分解成基本數據類型),直接跨網傳遞對象。
 可移動屬性:RMI可將屬性(類實現程序)從客戶機移動到服務器,或者從服務器移到客戶機。例如,您可以定義一個檢查僱員開支報告的接口,以便察看僱員是否遵守了公司目前實行的政策。在開支報告創建後,客戶機就會從服務器端獲得實現該接口的對象。如果政策發生變化,服務器端就會開始返回使用了新政策的該接口的另一個實現程序。您不必在用戶系統上安裝任何新的軟件就能在客戶端檢查限制條件--從而向用戶提供爍快的反饋,並降低服務器的工作量。這樣就能具備最大的靈活性,因爲政策改變時只需要您編寫一個新的Java類,並將其在服務器主機上安裝一次即可。
 設計方式:對象傳遞功能使您可以在分佈式計算中充分利用面向對象技術的強大功能,如二層和三層結構系統。如果您能夠傳遞屬性,那麼您就可以在您的解決方案中使用面向對象的設計方式。所有面向對象的設計方式無不依靠不同的屬性來發揮功能,如果不能傳遞完整的對象--包括實現和類型--就會失去設計方式上所提供的優點。
 安  全:RMI使用Java內置的安全機制保證下載執行程序時用戶系統的安全。RMI使用專門爲保護系統免遭惡意小應用程序侵害而設計的安全管理程序,可保護您的系統和網絡免遭潛在的惡意下載程序的破壞。在情況嚴重時,服務器可拒絕下載任何執行程序。
便於編寫和使用:RMI使得Java遠程服務程序和訪問這些服務程序的Java客戶程序的編寫工作變得輕鬆、簡單。遠程接口實際上就是Java接口。服務程序大約用三行指令宣佈本身是服務程序,其它方面則與任何其它Java對象類似。這種簡單方法便於快速編寫完整的分佈式對象系統的服務程序,並快速地製做軟件的原型和早期版本,以便於進行測試和評估。因爲RMI程序編寫簡單,所以維護也簡單。
 可連接現有/原有的系統:RMI可通過Java的本機方法接口JNI與現有系統進行進行交互。利用RMI和JNI,您就能用Java語言編寫客戶端程序,還能使用現有的服務器端程序。在使用RMI/JNI與現有服務器連接時,您可以有選擇地用Java重新編寫服務程序的任何部分,並使新的程序充分發揮 Java的功能。類似地,RMI可利用JDBC、在不修改使用數據庫的現有非Java源代碼的前提下與現有關係數據庫進行交互。
 編寫一次,到處運行:RMI是Java“編寫一次,到處運行 ”方法的一部分。任何基於RMI的系統均可100%地移植到任何Java虛擬機上,RMI/JDBC系統也不例外。如果使用RMI/JNI與現有系統進行交互工作,則採用JNI編寫的代碼可與任何Java虛擬機進行編譯、運行。
 分佈式垃圾收集:RMI採用其分佈式垃圾收集功能收集不再被網絡中任何客戶程序所引用的遠程服務對象。與Java 虛擬機內部的垃圾收集類似,分佈式垃圾收集功能允許用戶根據自己的需要定義服務器對象,並且明確這些對象在不再被客戶機引用時會被刪除。
 並行計算:RMI採用多線程處理方法,可使您的服務器利用這些Java線程更好地並行處理客戶端的請求。Java分佈式計算解決方案:RMI從JDK 1.1開始就是Java平臺的核心部分,因此,它存在於任何一臺1.1 Java虛擬機中。所有RMI系統均採用相同的公開協議,所以,所有Java 系統均可直接相互對話,而不必事先對協議進行轉換。

傳遞屬性


  前面我們講到,RMI可以傳遞屬性,並簡單介紹了一下一個有關開支報告程序的情況。下面我們將深入討論如何設計這樣的系統。這樣介紹的目的是使您能夠利用RMI的功能將屬性從一個系統傳遞到另一個系統,並隨心所欲地安排當前的計算地點,並便於將來的改變。下面的例子並未涉及真實世界可能發生的所有問題,但可幫助讀者瞭解處理問題的方法。

服務器定義的策略

  圖1是可進行動態配置的開支報告系統的示意圖。客戶機向用戶顯示圖形用戶界面(GUI),用戶填寫開支報告。客戶機程序使用RMI與服務器進行通信。服務器使用JDBC( Java關係數據庫連接包)將開支報告存儲在數據庫中。至此,這看起來與其它多層次系統大同小異,但有一個重大區別-- RMI能下載屬性。
假定公司關於開支報告的政策發生改變。例如,目前公司只要求對超過20美元的開支需開具發票。但到明天,公司認爲這太寬鬆了,便決定除不超過20美元的餐費以外,任何開支均需開具發票。如果不能下載屬性的話,那麼在設計便於修改的系統時您可選擇下列方法之一:

  用客戶端安裝與政策有關的程序。政策改變時,必須更新包含此政策的所有客戶端程序。您可在若干服務器上安裝客戶程序,並要求所有用戶從這些服務器之一運行客戶程序,從而減少問題。但這仍不能徹底解決問題-- 那些讓程序運行好幾天的用戶就無法使程序更新,而總是會有一些用戶爲了方便而把軟件複製到本地磁盤上。
  您可要求服務器在每次向開支報告添加項目時檢查政策。但這樣就會在客戶機和服務器之間產生大量數據流,並增加服務器的工作量。這還會使系統變得更加脆弱--網絡故障會立即妨礙用戶,而不僅僅是隻在其呈交開支報告或啓動新的報告時對其產生影響。同時,添加項目的速度也會變慢,因爲這需要穿越整個網絡往返一圈才能到達(不堪重負的)服務器。
您可在呈交報告時要求服務器對政策進行檢查。這樣就會使用戶創建很多必須待批報告的錯誤項目,而不是立刻捕捉到第一個錯誤,從而使用戶有機會停止製造錯誤。爲避免浪費時間,用戶需要立刻得到有關錯誤的反饋。
  有了RMI,您就能以簡單的方法調用程序從服務器得到屬性,從而提供了一種靈活的方式,將計算任務從服務器卸載到客戶機上,同時爲用戶提供速度更快的反饋。當用戶準備編寫一份新的開支報告時,客戶機就會向服務器要求一個對象,該對象嵌入了適用於該開支報告的當前政策,就如同通過用Java編寫的政策接口所表示的那樣。該對象可以以任何方式實現當前政策。如果這是客戶機RMI首次看到這種專門執行的政策,就會要求服務器提供一份執行過程的副本。如果執行過程將來發生變化,則一種新的政策對象將被返回給客戶機,而RMI運行時則會要求得到新的執行過程。
  這表明,政策永遠是動態的。您要想修改政策,就只需簡單地編寫通用政策接口的新的執行程序,把它安裝在服務器上,並對服務器進行配置以返回這種新類型的對象即可。這樣,每臺客戶機都會根據新的政策對新的開支報告進行檢查。

這是一種比任何靜態方法都更好的方法,原因如下:


  所有客戶機不必暫停或用新的軟件來升級--軟件可根據需要在不工作時加以更新。
  服務器不必參與項目檢查工作,該工作可在本地完成。
  允許動態限制,因爲對象執行程序(而不僅僅是數據)是在客戶機和服務器之間進行傳遞的。
  使用戶能立刻看到錯誤。


使客戶機在服務器上所能調用的方法的遠程接口定義如下:
import java.rmi.*;
public interface ExpenseServer extends Remote {
Policy getPolicy() throws RemoteException;
void submitReport(ExpenseReport report)
throws RemoteException, InvalidReportException;
}
import語句輸入Java RMI包。所有RMI類型均在包java.rmi或其子包內定義。接口ExpenseServer是一般的Java接口,具有如下兩種有趣的特點:


它擴展了名爲Remote的RMI接口,這使該接口標記爲可用於遠程調用。


它的所有方法均拋出RemoteException,後者用來表示網絡或信息故障。

  遠程方法還可拋出您所需要的任何其他例外,但至少必須拋出RemoteException,這樣您才能處理只會在分佈式系統中發生的錯誤狀態。該接口本身支持兩個方法:getPolicy (返回一個實現政策接口的對象),和submitReport (提交一個完成的開支請求,並在報告無論因何種原因使表格出現錯誤時,拋出一個例外)。
政策接口本身可聲明一種使客戶機知道能否在開支報告中添加一個項目的方法:
public interface Policy {
void checkValid (Expenseentry entry)
throws PolicyViolationException;
}
如果該項目有效--即符合當前政策,則該方法可正常返回。否則就會拋出一個描述該錯誤的例外。政策接口是本地的(而非遠程的),所以將通過本機對象在客戶機上執行--在客戶機的虛擬機上,而非整個網絡上運行的對象。客戶機可能運行下列程序:
Policy curPolicy = server.getPolicy();
start a new expense report
show the GUI to the user
while (user keeps adding entries) {
try {
curPolicy.checkValid(entry); // throws exception if not OK
add the entry to the expense report
} catch (PolicyViolationException e) {
show the error to the user
}
}
server.submitReport(report);


  當用戶請求客戶機軟件啓動一份新的開支報告時,客戶機就調用server.getPolicy,並要求服務器返回一個包含當前開支政策的對象。添加的每個項目首先都被提交給該政策對象,以獲得批准。如果政策對象報告無錯誤,則該項目就被添加到報告中;否則錯誤就被顯示給用戶,而後者可採取修正措施。當用戶完成向報告中添加項目時,整個報告就被呈交。服務程序如下:
import java.rmi. *;
import java.rmi.server. *;
class ExpenseServerImpl
extends UnicastRemoteObject
implements ExpenseServer
{
ExpenseServerImpl() throws RemoteException {
// . . . set up server state . . .
}
public Policy getPolicy() {
return new TodaysPolicy();
}
public void submitReport(ExpenseReport report) {
// . . . write the report into the db . . .
}
}
除基本程序包外,我們還輸入RMI的服務程序包。類型UnicastRemoteObject 定義了此服務程序遠程對象的類型,在本例中,應爲單一服務程序而非複製服務(下面還會詳細介紹)。Java類ExpenseSeverImpl實現遠程接 ExpenseServer的方法。遠程主機的客戶機可使用RMI將信息發送給ExpenseServerImpl對象。


本文中討論的重要方法是getPolicy,它簡單地返回定義當前政策的對象。下面看一個執行政策的例子:

public class TodaysPolicy implements Policy {
public void checkValid(ExpenseEntry entry)
throws PolicyViolationException
{
if (entry.dollars() < 20) {
return; // no receipt required
else if (entry.haveReceipt() == false) {
throw new PolicyViolationException;
}
}
}
TodaysPolicy進行檢查的目的是確保無收據的任何項目均少於20美元。如果將來政策發生變化,僅少於20美元的餐費可不受“需要收據”政策的限制,則您可提供新的政策實現:
public class TomorrowsPolicy implements Policy {
public void checkValid(ExpenseEntry entry)
throws PolicyViolationException
{
if (entry.isMeal() && entry.dollars() < 20) {
return; // no receipt required
} else if (entry.haveReceipt() == false) {
throw new PolicyViolationException;
}
}
}
  編寫這個類,並把它安裝在服務器上,然後告訴服務器開始提供TomorrowsPolicy對象,而非daysPolicy對象,這樣您的整個系統就會開始使用新的政策。當客戶機調用服務器的getPolicy方法時,客戶機的RMI就會檢查返回的對象是否爲已知類型。每臺客戶機首次遇到 TomorrowsPolicy時,RMI就會在getPolicy返回前下載政策的實現。客戶機可輕鬆地開始增強這個新的政策。


  RMI使用標準Java對象序列化機制傳遞對象。引用遠程對象參數作爲遠程引用傳遞。如果向某方法提供的參數爲原始類型或本機(非遠程)對象,則向服務器傳遞一個深副本。返回值也拾照同樣的方式處理,只不過是沿其它方向。RMI可使用戶向本機對象傳遞和返回完整對象圖併爲遠程對象傳遞和返回引用。

  在真實的系統中,getPolicy方法可能會有一個可以識別用戶及開支報告類型(差旅、客戶關係等)的參數,這樣可使政策加以區別。您或者可以不要求單獨的政策和開支報告對象,但您可以有一種newExpenseReport方法,它可返回一個直接檢查政策的ExpenseReport對象。這最後一種策略可使您像修改政策一樣簡單地修改開支報告的內容--當公司決定需要把餐費劃分爲早餐、午餐和晚餐項目,而且像上述修改政策一樣簡單地執行修改時- -可編寫一個實現該報告的新類,客戶程序就會自動使用這個類。

計算服務器

  開支報告的例子表示了客戶機如何從服務器得到屬性。屬性可沿兩個方向傳遞--客戶機也可將新的類型傳遞給用戶。最簡單的例子就是如圖2所示的計算服務器,該服務程序可執行任意任務,這樣整個企業內的客戶機都能利用高端或專用計算機。

任務由一個簡單的本地(非遠程)接口定義:
public interface Task {
Object run();
}
運行時,它就會進行一些計算,並返回一個包含結果的對象。這完全是一般性的任務--幾乎所有計算任務都可在這個接口下實現。遠程接口ComputeServer也同樣簡單:
import java.rmi.*;
public interface ComputeServer extends Remote {
Object compute(Task task) throws RemoteException;
}
這個遠程接口的唯一目的就是使客戶機創建一個Task (任務)對象,並把它發送給服務器執行,最後返回結果。該服務器的基本實現如下:
import java.rmi.*;
import java.rmi.server.*;
public class ComputeServerImpl
extends UnicastRemoteObject
implements ComputeServer
{
public ComputeServerImpl() throws RemoteException { }
public Object compute(Task task) {
return task.run();
}
public static void main(String[] args) throws Exception {
// use the default, restrictive security manager
System.setSecurityManager(new RMISecurityManager());
ComputeServerImpl server = new ComputeServerImpl();
Naming.rebind("ComputeServer", server);
System.out.println("Ready to receive tasks");
return;
}
}


  如果您看一看compute方法就會發現,它非常簡單。它只是接受傳遞給它的對象,並返回計算的結果。main方法包括服務器的啓動代碼--它安裝了 RMI的缺省安全管理程序,以防止他人存取本地系統,並創建可處理進入的請求的ComputeServerImpl對象,並將其與名字 "ComputeServer"關聯。這時,服務器已經準備好接收要執行的任務,而main 也完成了其設置。
如上所述,這實際上是一種全面和實用的服務。系統可以得到改進,比如,可添加要計算的參數,從而對使用服務程序的部門進行計費。但在很多情況下,上述接口和及其實現允許使用高端計算機進行遠程計算。這又明瞭RMI的簡單性--如果您鍵入上述類,對其進行編譯,並啓動服務程序,您就擁有了能執行任意任務的運行計算服務器。

  下面介紹一個使用這種計算服務的例子。假定您購買了一個能運行大量計算操作應用程序的非常高端的系統。管理員可在該系統上啓動一個Java虛擬機,運行ComputeServerImpl對象。該對象現在就可接受要運行的任務。

現在假定一個小組準備通過一組數據培訓一個神經網絡,以幫助制訂採購策略。他們可以採用的步驟如下:

  定義一個類--暫且稱之爲PurchaseNet。它能接受一組數據,並運行培訓數據,返回一個經過培訓的神經網絡。PurchaseNet 將實現Task (任務)接口,並在其run方法中執行其工作。他們可能還需要一個Neuron類來描述所返回的網絡中的節點,而且很可能需要其它類來描述處理過程。 run方法將返回一個由一組經過培訓的Neuron對象組成的NeuralNet對象。


當這些類被編寫好並進行小規模測試時,用一個PurchaseNet 對象調用ComputeServer的compute方法。
當ComputeServerImpl對象中的RMI系統接收到作爲進入參數的 PurchaseNet對象時,它就下載PurchaseNet的實現,並調用該服務器的compute方法,並把該對象作爲Task (任務)參數。
Task,也就是PurchaseNet對象,將開始執行其執行程序。當執行程序需要諸如Neuron和NeuralNet等新的類時,它們可根據需要下載。
所有計算都將在計算服務器上執行,而客戶機線程則等待結果。(客戶機系統上的另一個線程則會顯示“正在等待”光標或使用Java的內置並行機制執行另一個任務 )。當運行返回其NeuralNet對象時,這個對象將作爲compute 方法的結果被傳遞迴客戶機。
這不需要在服務器上安裝其它軟件--所有必須完成的任務都由各部門在其自己的計算機上完成,隨後再根據需要傳遞給計算服務器主機。
這個簡單的計算服務器體系結構爲系統的分佈式功能提供了功能強大的轉換能力。一項任務的計算可以被轉移到一個能爲其提供最好支持的系統上完成。這樣的系統可以被用來:

  在ComputeServerImpl對象運行於有數據挖掘需要的主機上,支持數據挖掘應用程序。這樣可使您輕鬆地把任何計算移動到數據所在的地方。
在從當前股票價格、發貨信息或其它實時信息等外部資源獲得直接數據的服務器上運行計算任務。
通過運行ComputeServer (接受進入的請求並將其轉送到運行 ComputeServerImpl的負擔最小的服務器上)的不同實現,而將任務分佈在多個服務器上。
代理
因爲RMI允許使用Java實現下載屬性,所以您可使用RMI 編寫代理系統。代理的最簡單格式如下:

import java.rmi.*;
public interface AgentServer extends Remote {
void accept(Agent agent)
throws RemoteException, InvalidAgentException;
}
public interface Agent extends java.io.Serializable {
void run();
}
啓動一個代理也就創建了實現Agent (代理)接口、找到服務器、激活接受該代理對象的類。該代理的執行程序將被下載到服務器上運行。accept方法將啓動一個該代理的新線程,激活其run 方法,然後返回,從而使該代理一直執行到run方法返回爲止。代理可通過激活在另一臺主機上運行的服務程序的accept方法而移植到該主機,而其本身則作爲將被接受的代理來傳遞,並結束其在原來主機上的線程。
面向對象的代碼重用與設計模式

  面向對象的編程是一項允許代碼重用的強大技術。很多企業組織都使用面向對象的編程來減輕創建程序的負擔和提高系統的靈活性。RMI是完全面向對象的--信息被髮送給遠程對象,而且對象可以被傳遞和返回。

Design Patterns (設計模式)目前在描述面向對象設計的實踐活動中獲得了相當大的成功。首先是因爲Design Patterns 的創新工作而使之大受歡迎,這些編程方式是一種正式描述解決某類特定問題的完整方法的途徑。所有這些設計模式都依賴於創建一個或多個抽象概念,這些抽象概念允許不同的實現,從而允許和增強軟件重用。軟件重用是面向對象技術的主要優勢,而設計模式則是促進重用的最受歡迎的技術之一。

所有設計模式都依賴面向對象的多態性--這是對象( 如Task )擁有多個實現的能力。算法的普通部分(如compute 方法)不必知道使用了哪個實現,它只需知道得到一個對象後應該對該對象採取什麼操作。特別地,計算服務器就是Command (指令)模式的一個例子,它可使您將請求 (任務)表示爲對象,並對其進行調度。

只有當包括執行程序在內的完整對象能在客戶機和服務器之間傳遞時,纔會存在這樣的多態性。DCE和DCOM等傳統的RPC系統以及CORBA等基於對象的RPC系統不能下載並執行程序,因爲它們不能把真實對象作爲參數傳遞,而只能傳遞數據。

  RMI可傳遞包括執行程序在內的所有類型,所以您可以在分佈式計算解決方案中,而不僅僅是本地計算中使用面向對象的編程--包括設計方式。如果沒有RMI這樣完全面向對象的系統,那麼您就必須放棄很多分佈式計算系統中的設計方式--以及面向對象的軟件重用的其它形式。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章