RMI(即Remote Method Invoke 遠程方法調用)。在Java中,只要一個類extends了java.rmi.Remote接口,即可成為存在於服務器端的遠程對象,供客戶端訪問並提供一定的服務。JavaDoc描述:Remote 接口用於標識其方法可以從非本地虛擬機上調用的接口。任何遠程對象都必須直接或間接實現此接口。只有在“遠程接口”(擴展 java.rmi.Remote 的接口)中指定的這些方法才可遠程使用。
注意:extends了Remote接口的類或者其他接口中的方法若是聲明拋出了RemoteException異常,則表明該方法可被客戶端遠程訪問調用。
同時,遠程對象必須實現java.rmi.server.UniCastRemoteObject類,這樣才能保證客戶端訪問獲得遠程對象時,該遠程對象將會把自身的一個拷貝以Socket的形式傳輸給客戶端,此時客戶端所獲得的這個拷貝稱為“存根”,而服務器端本身已存在的遠程對象則稱之為“骨架”。其實此時的存根是客戶端的一個代理,用於與服務器端的通信,而骨架也可認為是服務器端的一個代理,用於接收客戶端的請求之后調用遠程方法來響應客戶端的請求。
RMI 框架的基本原理大概如下圖,應用了代理模式來封裝了本地存根與真實的遠程對象進行通信的細節。
下面給出一個簡單的RMI 應用,其中類圖如下:其中IService接口用於聲明服務器端必須提供的服務(即service()方法),ServiceImpl類是具體的服務實現類,而Server類是最終負責注冊服務器遠程對象,以便在服務器端存在骨架代理對象來對客戶端的請求提供處理和響應。

各個類的源代碼如下:
IService接口:
package limeRMI.service; import java.rmi.Remote; import java.rmi.RemoteException; public interface IService extends Remote{ // 聲明服務器端必須提供的服務 String service(String context) throws RemoteException; }
ServiceImpl實現類:
package limeRMI.service; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; //UnicastRemoteObject用於導出的遠程對象和獲得與該遠程對象通信的存根。 public class ServiceImpl extends UnicastRemoteObject implements IService{ /** * */ private static final long serialVersionUID = 1L; private String name; protected ServiceImpl(String name) throws RemoteException { this.name = name; } public String service(String context) throws RemoteException { return "server >> " + context; } }
Server類:
package limeRMI.service; import java.rmi.RemoteException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class Server { public static void main(String[] args) { try { // 實例化實現了IService接口的遠程服務ServiceImpl對象 IService serviceLime = new ServiceImpl("serviceLime"); // Context接口表示一個命名上下文,它由一組名稱到對象的綁定組成。 它包含檢查和更新這些綁定的一些方法。 // InitialContext類是執行命名操作的初始上下文。 該初始上下文實現 Context 接口並提供解析名稱的起始點 // 初始化命名空間 Context namingContext = new InitialContext(); // 將名稱綁定到對象,即向命名空間注冊已經實例化的遠程服務對象 namingContext.rebind("rmi://192.168.1.5:6666/serviceLime", serviceLime); System.out.println("服務器向命名表注冊了1個遠程服務對象! -- " + serviceLime.getClass().getSimpleName()); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NamingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Client類:
package limeRMI.client; import java.rmi.RemoteException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import limeRMI.service.IService; public class Client { public static void main(String[] args) { String url = "rmi://192.168.1.5:6666/"; try { Context namingContext = new InitialContext(); // 檢索指定的對象。 即找到服務器端相對應的服務對象存根 IService serviceLime = (IService) namingContext.lookup(url + "serviceLime"); Class<? extends IService> serviceLimeClazz = serviceLime.getClass(); System.out.println(serviceLime + " 是 " + serviceLimeClazz.getName() + " 的實例。 "); Class<?>[] interfaces = serviceLimeClazz.getInterfaces(); for(Class inter : interfaces){ System.out.println("存根類實現了 " + inter.getName() + " 接口!"); } System.out.println(serviceLime.service("你好!")); } catch (NamingException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } } }
將以上代碼保存於某一目錄下,先運行“start rmiregistry”來啟動JDK自帶的注冊表程序,它用於保存Server類注冊的遠程對象並允許遠程客戶端的請求訪問;然后運行服務器端的Server類,即“start java Server”,該程序向注冊表中注冊具體的遠程對象;最后才是運行客戶端程序來查找並獲得服務器端的遠程對象存根,此時才能使用存根對象與服務器進行通信,命令是“java Client”。注意:上面命令中的start的功能是重新打開一個DOS窗口。
其實整個簡單的RMI 應用中各個類的交互時序如下圖:

啦啦啦
