Java遠程方法調用2

傳遞屬性


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

服務器定義的策略
rmi_white_paper_image.gif
  圖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對象。這最後一種策略可使您像修改政策一樣簡單地修改開支報告的內容--當公司決定需要把餐費劃分爲早餐、午餐和晚餐項目,而且像上述修改政策一樣簡單地執行修改時--可編寫一個實現該報告的新類,客戶程序就會自動使用這個類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章