Java 2 Enterprise Edition(J2EE)遠程方法調用(Remote Method Invocation,RMI)框架允許你創建透明的、分布式的服務和應用程序。基於RMI的應用程序由Java對象構成,這些對象相互調用,同時忽略對方的位置。換言之,一個Java對象可調用另一個虛擬機上的某個Java對象的方法,整個過程和調用同一個虛擬機上的某個Java對象的方法無異。
駐留在不同虛擬機上的對象為了相互獲得引用,可以使用RMI的查找服務,或者將對象引用作為方法調用的一個參數或者返回值來接收。參數和返回值借助Java的對象序列化機制由RMI來進行封送。
遠程對象和接口
Java提供了一個完全限定名稱為java.rmi.Remote的接口。任何對象要想參與和另一個Java對象的遠程會話,就必須直接或間接地實現該接口。尤其要注意的是,任何由java.rmi.Remote接口來標識的對象都暗示著它的方法可從其他任何虛擬機進行調用。實現了Java.rmi.Remote接口的對象通常稱為“遠程對象”,必須采用以下方式來聲明它的方法:
每個支持遠程調用的方法都必須在其throws子句中聲明Java.rmi.RemoteException。
對於一個可遠程調用的方法,它的每個非基本(nonprimitive)參數或者返回值都必須直接或間接地聲明為實現了Java.io.Serializable接口。
除了實現java.rmi.Remote接口和正確聲明任何遠程方法之外,遠程對象必須提供一個無參數的構造函數,它能引發一個Java.rmi.RemoteException異常。這就保證了對象可基於一種序列化狀態來遠程構造。
遠程對象必須導出,以接收傳入的遠程方法調用。為此,你通常需要擴展java.rmi.server.UnicastRemoteObject或者Java.rmi.activation.Activatable。通過對其中任何一個類進行擴展,遠程對象就可在創建時自動導出。
以下接口定義展示了Java.rmi.Remote接口最典型的用法:
import Java.rmi.Remote;
import Java.rmi.RemoteException;
public interface TimeKeeper extends Remote
{
public String currentDate() throws RemoteException;
public String currentTime() throws RemoteException;
}
由於String類聲明為實現了Java.io.Serializable接口,所以String是遠程方法的有效返回類型。
以下代碼展示了如何實現TimeKeeper接口,以便定義一個有效的遠程對象:
import Java.rmi.RemoteException;
import Java.util.Calendar;
import Java.util.GregorianCalendar;
public class TimeKeeperImpl implements TimeKeeper
{
public TimeKeeperImpl()
throws RemoteException
{
}
public String currentDate() throws RemoteException
{
Calendar cal = new GregorianCalendar();
String retVal = (cal.get(Calendar.MONTH) + "/" +
cal.get(Calendar.DAY_OF_MONTH) + "/" +
cal.get(Calendar.YEAR));
return retVal;
}
public String currentTime() throws RemoteException
{
Calendar cal = new GregorianCalendar();
String retVal = (cal.get(Calendar.HOUR_OF_DAY) + ":" +
cal.get(Calendar.MINUTE) + ":" +
cal.get(Calendar.SECOND));
return retVal;
}
}
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客戶端如何定位遠程對象,並調用其上的方法。