向您介紹使用RMI實現Java的分佈式計算。因爲Java具備跨平臺、代碼可移植性、安全高效等普遍而強大的功能,於是在開發網絡Java分佈式應用的時候,能夠用它自身的機制實現分佈式計算。
概述java
隨着電力企業信息化建設的不斷深刻和發展,企業內部和企業與企業之間對信息、對數據的交換量大大增長,這些信息與數據愈來愈須要在不一樣的計算機網絡間傳送和交流。同時,因爲各單位、各部門之間的現存的計算機網絡硬件設備與操做系統千差萬別,應用水平也良莠不齊,所以,開發出跨平臺、可移植、高效安全的網絡分佈式應用來服務於電力企業,就顯得尤其重要。程序員
在當今的編程術語裏,分佈式計算已經成爲很常見的詞,它將企業的業務數據和程序分佈在網絡的不一樣物理位置上,經過調動網絡上多臺計算機的處理能力,發揮遠程調用數據的功能。編程
遠程方法調用(Remote Method Invocation ,RMI),能夠在不一樣的Java虛擬機(JVM)之間實現對象與對象的通訊。JVM能夠位於相同或不一樣計算機上,在多個JVM中,一個JVM能夠調用存儲在其它JVM的對象的方法。安全
本文主要介紹RMI的特色,分析應用RMI進行企業分佈式計算的原理,以及利用RMI實現基於Java的企業分佈式應用的具體步驟。服務器
遠程方法調用(RMI)的特色網絡
一、TCP編程的缺點框架
因爲Java編程語言設計之初就是面向對象和支持網絡的,所以,基於對象的RMI機制已經內置在Java分佈式計算平臺中。編程語言
咱們常常會在網絡開發中使用TCP/IP編程,這樣,天然而然地就會涉及到Socket(套接字)編程。可是,使用Socket編程須要大量重複編碼,在複雜分佈式操做時顯得很是麻煩,並且易於出錯。所以,如何快速、高效、安全、可擴展地進行網絡分佈式計算,是開發者們一向追求和倡導的主題。直到RMI的出現,這種繁雜、低效的開發狀況纔有很大改觀。分佈式
二、RMI編程的特色ide
當咱們利用對象序列化在網絡上分配對象時,RMI提供了非Java平臺沒法匹敵的獨特而強大的分佈式計算模型,RMI主要有如下特色:
客戶機能夠向本地方法同樣調用遠程服務器上的方法;
能夠根據接口指定客戶機/服務器編程合約;
能夠從服務器對象缺省二進制類文件,自動生成調動/反調動代碼;
將Java編程模型擴展到機器邊界(和Java虛擬機(JVM)邊界以外),不須要任何特殊語法;
還能夠和一個遠程方法調用中的數據同時傳輸行爲(代碼)。
儘管RMI不是惟一的企業級遠程對象訪問方案,但它倒是最容易實現的。
三、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的基本體系結構,歸納起來講,由三個抽象層組成:
一、存根/框架層(Stubs/Skeletons Layer)
RMI爲咱們引入了兩種特殊類型的對象,稱爲存根(Stub)和框架(Skeleton),它們組成了RMI的第一層。
在遠程通訊的時候,要利用TCP/IP協議,作不少底層數據的打包傳輸。運用Java分佈式計算技術,咱們先要把數據或者對象轉換成字節流(byte stream),便於網絡傳輸,這個過程叫聚集(marshaling);當收到遠程傳來的字節流後,咱們要把流信息轉換成對象或者數據,這個過程叫解讀(unmarshaling),它與聚集恰好相反。
Stub和Skeleton層位於實際應用程序之下,創建在Proxy(代理)設計方案之上。Stub類的做用是遠程服務器實現的代理的角色,Stub是客戶方對象;Skeleton類用於幫助對象經過RMI連接與Stub通訊,它從鏈路中讀取方法調用的參數,向遠程服務實現對象進行調用,接受返回值,而後再把返回值寫回到Stub。
二、遠程引用層(Remote Reference Layer)
遠程引用層定義和支持着RMI鏈接的調用語義(semantics)。
RMI進行遠程訪問要用到JRMP(Java Remote Method Protocol,即Java遠程方法協議),這一層提供專用於JRMP的RemoteRef對象,它位於java.rmi.server包內,表明着遠程對象的一個句柄。RemoteRef使用遠程引用來執行遠程對象的一個遠程方法調用。
三、傳輸層(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規範的類和接口模型,使這些類和接口之間互相協做,能實現各自獨立的功能,最後,能夠把它們組合成統一的網絡Java分佈式計算系統。
如今,咱們就以開發公司信息發佈系統爲例,把主模塊(主要的類文件)的名稱暫定爲InfoDistributeService(信息發佈服務),爲了保持應用開發的數據一致性和清晰度,接下來涉及的其它模塊命名也將以這個模塊命名爲基準。
一、定義遠程接口
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;
- }
二、實現遠程接口
這是一個實現遠程對象的類。若是實現了遠程接口,就可以覆蓋(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。因爲符
Java 2 Enterprise Edition(J2EE)遠程方法調用(Remote Method Invocation,RMI)框架容許你建立透明的、分佈式的服務和應用程序。基於RMI的應用程序由Java對象構成,這些對象相互調用,同時忽略對方的位置。換言之,一個Java對象可調用另外一個虛擬機上的某個Java對象的方法,整個過程和調用同一個虛擬機上的某個Java對象的方法無異。
駐留在不一樣虛擬機上的對象爲了相互得到引用,可使用RMI的查找服務,或者將對象引用做爲方法調用的一個參數或者返回值來接收。參數和返回值藉助Java的對象序列化機制由RMI來進行封送。
遠程對象和接口
Java提供了一個徹底限定名稱爲java.rmi.Remote的接口。任何對象要想參與Java分佈式計算和另外一個Java對象的遠程會話,就必須直接或間接地實現該接口。尤爲要注意的是,任何由java.rmi.Remote接口來標識的對象都暗示着它的方法可從其餘任何虛擬機進行調用。實現了java.rmi.Remote接口的對象一般稱爲「遠程對象」,必須採用如下方式來聲明它的方法:
每一個支持遠程調用的方法都必須在其throws子句中聲明java.rmi.RemoteException。
對於一個可遠程調用的方法,它的每一個非基本(nonprimitive)參數或者返回值都必須直接或間接地聲明爲實現了java.io.Serializable接口。
除了實現java.rmi.Remote接口和正確聲明任何遠程方法以外,Java分佈式計算中遠程對象必須提供一個無參數的構造函數,它能引起一個java.rmi.RemoteException異常。這就保證了對象可基於一種序列化狀態來遠程構造。
遠程對象必須導出,以接收傳入的遠程方法調用。爲此,你一般須要擴展java.rmi.server.UnicastRemoteObject或者java.rmi.activation.Activatable。經過對其中任何一個類進行擴展,遠程對象就可在建立時自動導出。
RMI註冊表
爲了獲取對遠程對象的引用,RMI提供了名爲註冊表(registry)的一個遠程對象,它將名稱與遠程對象關聯起來。RMI服務器要向註冊表註冊每個遠程對象,以便定位和檢索對象。RMI客戶端但願調用遠程對象上的一個方法時,首先必須根據遠程對象的名稱在註冊表中定位遠程對象。若是遠程對象存在,註冊表就返回對那個對象的一個引用。而後,要使用這個引用來發出對遠程對象的方法調用。
RMI服務器
RMI採起一種客戶機/服務器結構進行通訊。這意味着在RMI會話的某一端,必須有一個對象充當服務器,另外一端的對象則充當客戶端。RMI服務器負責建立每一個遠程對象的實例,並將每一個實例和RMI註冊表中的一個名稱綁定起來。RMI服務器能夠自主,這要求它實現一個main方法,避免必須依賴其餘類才能執行。
因爲RMI服務器可從幾乎任何主機下載和執行代碼,因此每一個RMI服務器的main方法都須要安裝一個安全管理器,防止它所加載的類表現失常。下例展現瞭如何實例化一個安全管理器,以及如何在RMI註冊表中綁定一個對象實例:
- import java.rmi.RMISecurityManager;
- import java.rmi.Naming;
- public class SimpleRMIServer
- {
- public static void main(String[] args)
- {
- if (System.getSecurityManager() == null)
- {
- System.setSecurityManager(new RMISecurityManager());
- }
- try
- {
- TimeKeeperImplremoteObj = new TimeKeeperImpl();
- // Bind the remote object to the name "TimeKeeper"
- Naming.bind("//HostName/TimeKeeper", remoteObj);
- System.out.println("TimeKeeper successfully bound in registry");
- }
- catch (Exception e)
- {
- System.err.println("Error binding TimeKeeper: " + e.getMessage());
- }
- }
- }
小結
本文簡單介紹瞭如何用RMI來隱藏遠程交互問題,使程序員能將注意力集中在其餘更重要的問題上,而沒必要過多地考慮通訊基礎結構。下一篇文章將進一步探索RMI,講解RMI客戶端如何定位遠程對象,並調用其上的方法。
注:以上文章轉自51CTO