摘 要 由於Java具有跨平台、代碼可移植性、安全高效等廣泛而強大的功能,因而在開發網絡分布式應用的時候,可以用它自身的機制實現分布式計算,一種基於Java的遠程方法調用(RMI)為我們開發企業分布式應用提供了行之有效的解決方案。
關鍵詞 Java RMI 企業分布式應用
概述
隨著電力企業信息化建設的不斷深入和發展,企業內部和企業與企業之間對信息、對數據的交換量大大增加,這些信息與數據越來越需要在不同的計算機網絡間傳送和交流。同時,由於各單位、各部門之間的現存的計算機網絡硬件設備與操作系統千差萬別,應用水平也參差不齊,因此,開發出跨平台、可移植、高效安全的網絡分布式應用來服務於電力企業,就顯得尤為重要。
在當今的編程術語裡,分布式計算已經成為很常見的詞,它將企業的業務數據和程序分布在網絡的不同物理位置上,通過調動網絡上多台計算機的處理能力,發揮遠程調用數據的功能。
遠程方法調用(Remote Method Invocation ,RMI),可以在不同的Java虛擬機(JVM)之間實現對象與對象的通信。JVM可以位於相同或不同計算機上,在多個JVM中,一個JVM可以調用存儲在其它JVM的對象的方法。
本文主要介紹RMI的特點,分析應用RMI進行企業分布式計算的原理,以及利用RMI實現基於Java的企業分布式應用的具體步驟。
遠程方法調用(RMI)的特點
1、TCP編程的缺點
由於Java編程語言設計之初就是面向對象和支持網絡的,因此,基於對象的RMI機制已經內置在Java平台中。
我們經常會在網絡開發中使用TCP/IP編程,這樣,自然而然地就會涉及到Socket(套接字)編程。但是,使用Socket編程需要大量重復編碼,在復雜分布式操作時顯得非常麻煩,而且易於出錯。因此,如何快速、高效、安全、可擴展地進行網絡分布式計算,是開發者們一貫追求和倡導的主題。直到RMI的出現,這種繁雜、低效的開發情況才有很大改觀。
2、RMI編程的特點
當我們利用對象序列化在網絡上分配對象時,RMI提供了非Java平台無法匹敵的獨特而強大的分布式計算模型,RMI主要有以下特點:
客戶機可以向本地方法一樣調用遠程服務器上的方法;
可以根據接口指定客戶機/服務器編程合約;
可以從服務器對象缺省二進制類文件,自動生成調動/反調動代碼;
將Java編程模型擴展到機器邊界(和Java虛擬機(JVM)邊界之外),不需要任何特殊語法;
還可以和一個遠程方法調用中的數據同時傳輸行為(代碼)。
盡管RMI不是唯一的企業級遠程對象訪問方案,但它卻是最容易實現的。
3、RMI與CORBA
作為分布式應用程序框架的規范,COBRA首當其沖,它是由對象管理組織(OMG)開發的。與CORBA不同的是,CORBA能夠利用不同編程語言(例如C/C++、Basic等)開發實現分布式應用,而RMI是一種純Java解決方案。在RMI中,程序的所有部分都由Java語言編寫,這樣,開發出來的程序完全符合Java規范,便於實現跨平台訪問、擴展和移植。按照筆者所在西北電力建設集團公司的情況看,服務器操作系統主要有Linux和Windows2000 Server,分別存在於公司和部門當中,它們是不同的系統平台;同時,公司下屬各個工程項目部又距離很遠,近的幾十公裡,遠則達到上千公裡甚至位於國外,因此跨平台和遠程訪問這兩大功能在開發企業應用系統時就必須考慮,而RMI恰恰能夠用它的自身特點來滿足編程需要。
RMI基本體系結構簡介
RMI通過TCP/IP在內部使用Socket,象其名稱暗示的那樣,它能夠幫助我們查找並執行遠程對象的方法。RMI的目的是讓位於不同JVM中的對象,在外觀及行為上都像是本地的對象。
通常,我們把調用這種遠程對象的JVM,稱為客戶機;而把包括這種遠程對象的JVM,稱為服務器。
盡管對一個遠程對象的引用和獲得對本地對象的引用有所不同,但我們可以把遠程對象像本地對象一樣使用。應用程序並不知道一個對象是遠程的還是本地的。實際上,遠程對象上被調用的方法與本地對象上調用的方法,具有相同的語法結構。
作為RMI的底層(會包含復雜的Socket操作),它會自動截獲方法調用,找到遠程對象,然後處理遠程請求。筆者認為,RMI設計的重要之處,就在於不但在設計上實現了遠程訪問功能,而且實現了設計的透明性。
RMI的基本體系結構,概括起來說,由三個抽象層組成:
1、存根/框架層(Stubs/Skeletons Layer)
RMI為我們引入了兩種特殊類型的對象,稱為存根(Stub)和框架(Skeleton),它們組成了RMI的第一層。
在遠程通信的時候,要利用TCP/IP協議,做很多底層數據的打包傳輸。運用Java技術,我們先要把數據或者對象轉換成字節流(byte stream),便於網絡傳輸,這個過程叫匯集(marshaling);當收到遠程傳來的字節流後,我們要把流信息轉換成對象或者數據,這個過程叫解讀(unmarshaling),它與匯集剛好相反。
Stub和Skeleton層位於實際應用程序之下,建立在Proxy(代理)設計方案之上。Stub類的作用是遠程服務器實現的代理的角色,Stub是客戶方對象;Skeleton類用於幫助對象通過RMI鏈接與Stub通信,它從鏈路中讀取方法調用的參數,向遠程服務實現對象進行調用,接受返回值,然後再把返回值寫回到Stub。
2、遠程引用層(Remote Reference Layer)
遠程引用層定義和支持著RMI連接的調用語義(semantics)。
RMI進行遠程訪問要用到JRMP(Java Remote Method Protocol,即Java遠程方法協議),這一層提供專用於JRMP的RemoteRef對象,它位於java.rmi.server包內,代表著遠程對象的一個句柄。RemoteRef使用遠程引用來執行遠程對象的一個遠程方法調用。
3、傳輸層(Transport Layer)
傳輸層在JVM之間建立基於流的網絡連接,並且負責設置和管理這些連接。這時候,RMI使用一種線級(wire-level)協議進行基於TCP/IP的連接,該協議就是Java遠程方法協議(JRMP,即Java Remote Method Protocol)。
在JDK版本1.2開始,JRMP不再需要Skeleton,而是使用reflection來建立與遠程服務的連接。為了生成Stub,我們須用rmic。
當前的RMI實現中,傳輸層建立在TCP/IP基礎上,設計用於在客戶和服務器之間建立一條連接(即使聯網有障礙)。
開發的基本步驟
我們使用RMI編寫Client/Server模式(客戶/服務器)應用程序,包括6個基本步驟:
1) 定義遠程接口
2) 實現遠程接口
3) 准備遠程調用的服務器對象
4) 生成殘根Stub(客戶代理)和框架Skeleton(服務器實體)
5) 用rmiregistry找到遠程對象
6) 運行測試RMI分布式應用
開發企業信息發布系統實例
在開發RMI進行分布式訪問之前,需要將各項功能模塊化,即把實際應用抽象成符合Java規范的類和接口模型,使這些類和接口之間互相協作,能實現各自獨立的功能,最後,可以把它們組合成統一的網絡分布式系統。
現在,我們就以開發公司信息發布系統為例,把主模塊(主要的類文件)的名稱暫定為InfoDistributeService(信息發布服務),為了保持應用開發的數據一致性和清晰度,接下來涉及的其它模塊命名也將以這個模塊命名為基准。
1、定義遠程接口
Java RMI運行環境要求任何可以遠程調用的方法必須放在遠程接口中。
該遠程接口用來擴展java.rmi.Remote接口,在Java API中,可以發現它沒有任何方法,只是個標志性接口,這樣,可以讓Java運行環境(JRE)認識每個接口的特殊屬性,以便能夠遠程訪問。
因此,按照信息發布服務的命名(InfoDistributeService),首先須將InfoDistributeRemote定義為遠程接口,同時僅放入一個供測試的方法 getRemoteInfo()來實現編碼,將所有模塊至於新建的enterprise.distribute包中,代碼如下:
// -----------InfoDistributeRemote.java-------------------
package enterprise. distribute;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface InfoDistributeRemote extends Remote{
public String getRemoteInfo() throws RemoteException;
}
2、實現遠程接口
這是一個實現遠程對象的類。如果實現了遠程接口,就能夠覆蓋(override)該對象中的所有方法,因此,遠程對象的實現類將真正包含我們希望導出的方法的代碼。
在遠程信息發布系統中,我們至少實現一個遠程接口的對象,它就是遠程可訪問的對象。這裡,InfoDistributeService類可以為我們生成遠程可訪問對象的實例:
// -----------InfoDistributeService.java------------------
package enterprise. distribute;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class InfoDistributeService
extends UnicastRemoteObject implements InfoDistributeRemote{
public InfoDistributeService() throws RemoteException{
super();
}
// The return value of the method only for testing...
public String getRemoteInfo(){
return "Hello! I am a remote object.";
}
}
InfoDistributeService類實現遠程接口InfoDistributeRemote,並繼承java.rmi.server.UnicastRemoteObject。由於符合InfoDistributeRemote接口,因此該類除構造方法之外,還應有getRemoteInfo()方法,而且必須將該方法實現。
同時我們注意到,getRemoteInfo()方法拋出了java.rmi.RemoteException異常。由於遠程方法調用過程中,要進行很多的低級網絡操作,因此網絡錯誤可能在調用過程中隨時發生,這樣,遠程接口中的每個方法(盡管這裡只有一個getRemoteInfo()方法)都必須拋出RemoteException異常,而且,java.rmi.RemoteException都要在代碼中顯式處理,即將所有RMI活動涉及的代碼都要放在try-catch塊中。
3、准備供遠程調用的服務器對象
這是一個作為服務器使用的類,它是相對於要訪問遠程方法的客戶端而言的。它存儲著綁定的字符串和對象。
將遠程對象設置成接受遠程調用就像啟動了偵聽ServerSocket對象的socket服務器。事實上在使用RMI時,TCP/IP協議的傳輸方式,在幕後發生了很多底層網絡操作,但是此刻,用戶不需要知道這些細節,只需要逐步導出遠程對象:
在Java開發工具的較新版本JDK 1.4中,可以選擇java.rmi.UnicastRemoteObject或者java.rmi.Activation.Activatable。其中,更多用到的是UnicastRemoteObject,因為它顧名思義,就是在客戶機與服務器對象實例之間建立一對一連接。
4、生成Stub和Skeleton
由RMI生成的調動與反調動的遠程調動代碼也和Java的其它代碼一樣,都包含在後綴是.class的文件中。
運用RMI之後,在客戶機上生成的調動參數和反調動返回值的代碼稱為殘根(Stub),服務器上生成的反調動參數和進行實際方法調用調動返回值的代碼稱為框架(Skeleton)。
可以使用RMI自帶的命令行工具rmic(即RMI Compiler),先掃描遠程對象的.class文件,隨之生成殘根與框架代碼。工具rmic的原理如圖1。
5、遠程客戶端:這是一個幫助我們訪問遠程方法提供幫助的類,它也是最終用戶。我們將使用查找和調用遠程方法的方法在該類中調用遠程方法。
典型的rmic調用如下(在當前目錄): 我們有了遠程接口和實現,就可以用rmic對類進行編譯,生成Stub和Skeleton,在命令行窗口中使用以下代碼行:
// compile all java source files
javac enterprise\distribute\*.java
// make stub and skeleton code
rmic enterprise.distribute.InfoDistributeService
運行完畢後,在當前目錄生成下列文件(即調動代碼):
InfoDistributeService_Stub.class
InfoDistributeService_Skel.class
5、用rmiregistry找到遠程對象
導出服務器方的對象之後,就可以遠程訪問,但客戶機還要設法與這些遠程對象取得聯系。
由於分布式應用程序可能涉及許多不同機器,因此通信的所有機器對之間需要建立初始連接,換句話說,就是要設法找到初始遠程對象,這項工作通過rmiregistry命令執行。
JDK開發工具提供了實用程序RMI Registry,用來維護文本名和遠程對象之間的映射,可以進行遠程訪問。
在客戶機方,RMI注冊表可以通過同一java.rmi.Naming類的lookup()靜態方法用程序訪問。例如,遠程主機為iServer,遠程對象實例為InfoDistributeService實例,遠程端口號為5678,則客戶機如果查找遠程主機iServer中的遠程InfoDistributeService實例,那麼引用實例時須使用遠程接口InfoDistributeRemote,代碼如下:
InfoDistributeRemote iServer=( InfoDistributeRemote)Naming.lookup("rmi://iServer:5678/InfoDistributeService");
在服務器方,導出的遠程對象可以通過java.rmi.Naming類在本地注冊rmiregistry的運行實例。例如,用rebind()方法將iService中的InfoDistributeService的實例與名稱InfoDistributeService相關聯。
InfoDistributeService iService=new InfoDistributeService();
Naming.rebind("/ InfoDistributeService",iService);
綜上所述,InfoDistributeRemote是個Java接口,因此iServer實際上是實現InfoDistributeRemote接口的本地對象實例,它不在遠程,而是遠程InfoDistributeService的本地表示。也就是說,它是由前述rmic工具自動生成的殘根碼InfoDistributeService_Stub.class的本地實例。圖3顯示了遠程對象調用的工作原理。
圖2 遠程對象調用的工作原理
從圖2可以看出,客戶機實現通過iServer變量維護遠程對象的調用。事實上,iServer引用變量是實現InfoDistribute接口的對象的本地引用,該對象是InfoDistributeService殘根實現。這個殘根和服務器方的框架一起通過InfoDistributeRemote接口調動/反調動所有遠程調用。
當我們把InfoDistributeService看成是實現InfoDistributeRemote接口的本地實例的時候,殘根代碼只在幕後進行工作,而實現這一切則變得非常透明。
由上述原理,可以進一步設計完善客戶機和服務器代碼,進而編寫出完整的應用程序。
6、運行測試RMI分布式應用
在確認已經設計好必須的幾大類模塊後,我們開始按以下步驟,運行並測試該信息發布系統的基本功能(即僅僅實現遠程接口中聲明的getRemoteInfo()方法)。
在前面定義的包enterprise.distribute中,執行javac命令編譯後綴為.java的源文件。
javac enterprise/distribute/*.java
接著,用RMIC工具生成殘根與框架。
rmic enterprise.distribute.InfoDistributeService
編譯之後,需要確定客戶機與服務器發行版本的內容。因此,需用jar命令,將客戶機與服務器發行版本包裝成 .jar文件。 其中,包裝服務器的文件命令如下:
jar cvf InfoDistributeService.jar
enterprise\distribute\InfoDistributeService.class
enterprise\distribute\InfoDistributeRemote.class
enterprise\distribute\InfoDistributeService_Stub.class
enterprise\distribute\InfoDistributeService_Skel.class
同樣,包裝客戶機的類似於以上命令。
運行RMI應用程序
完成了第一階段的所有RMI試驗,然後運行信息發布應用程序。按照Java規范,需要依次啟動下列項目:
啟動RMIRegistry
在代碼目錄中用以下命令啟動rmiregistry實例,使之在控制台開始運行。
start rmiregistry
啟動服務器
直接啟動服務器,生成InfoDistributeService遠程對象的實例,並向注冊表注冊。
start java -classpath InfoDistributeService.jar
enterprise.distribute.InfoDistributeService
啟動客戶機
最後,用java -classpath RemoteClient.jar 命令啟動客戶機,然後通過rmiregistry找到遠程信息發布服務,再通過遠程調用得到所需要的遠程信息。
結論
本文簡要闡述了Java RMI的特點,以及用RMI開發企業分布式應用的主要步驟。以遠程信息發布系統為例,簡要地說明了遠程對象訪問、遠程方法調用在信息發布時的原理和實現過程。
為了開發出更符合實際的企業分布式應用,RMI還可以結合對象序列化實現更加強大的功能,為我們開發更加靈活、高效的網絡分布式應用系統提供方便。