RMI是Remote Method Invocation(遠程方法調用)的 所寫。它允許一個Java程序調用網絡中另一臺計算機上的Java方法,就如調用本機的方法一樣。實現(xiàn)RMI調用的程序和被調用的方法,都必須是Java代碼,即客戶端和服務器端都必須通過純Java實現(xiàn)。
RMI是基于Java的分布式編程模型,使用RMI進行遠程方法調用時,無須考慮方法底層的網絡傳輸細節(jié)。下面使用RMI的示例程序:
先編寫RMI服務器端,RMI需要通過遠程接口“暴露”服務。也就是說,所有想被客戶機調用的方法都必須在Remote接口里聲明,否則無法完成調用。遠程接口如下:
遠程接口必須集成java.rmi.Remote接口
public interface Server extends Remote
{
//所有在Remote接口里聲明的方法都必須拋出RemoteException異常
String helloWorld(String name) throws RemoteException;
Person getPerson(String name,int age)throws RemoteException;
}
遠程接口必須繼承java.rmi.Remote接口。遠程接口里聲明的方法會通過網絡傳輸,而網絡是不可靠的,因此,所有的遠程方法都必須拋出RemoteException。RMI服務是典型的面向接口編程,只有在遠程接口里定義的方法才會作為遠程服務。
下面是遠程服務提供類,遠程服務提供類必須實現(xiàn)遠程接口,并繼承java.rmi.server.UnicastRemoteObject對象,繼承該類能“暴露”遠程服務。
//遠程服務類,遠程服務類必須繼承UnicastRemoteObject,并實現(xiàn)Remote接口
public class ServerImpl extends UnicastRemoteObject implements Server
{
//遠程服務類必須擁有構造器,且構造器必須拋出RemoteException異常
public ServerImpl()throws RemoteException
{
}
//實現(xiàn)Remote接口必須實現(xiàn)的方法
public String helloWorld(String name)throws RemoteException
{
return name + ", 您好!";
}
//實現(xiàn)Remote接口必須實現(xiàn)的方法
public Person getPerson(String name,int age)throws RemoteException
{
return new Person(name,age);
}
//下面是服務類的本地方法,不會“暴露”為遠程服務。
public void info()
{
System.out.println(“我是本地方法”);
}
//下面提供程序入口,將遠程類實例綁定為本機的服務。
public static void main(String[] args)throws Exception
{
//創(chuàng)建遠程服務類實例
Server imp = new ServerImpl();
//注冊遠程服務的端口
LocateRegistry.createRegistry(1099);
//將遠程服務實例綁定為遠程服務。
Naming.rebind("rmi://:1099/fdf", imp);
}
}
遠程服務類必須有構造器,即使找個構造器什么都不做。而且,構造器必須拋出RemoteException異常。將兩個編輯好的源文件存盤,然后編譯。對于使用RMI,僅僅編譯還不夠,還必須使用rmic命令編譯服務類,編譯服務類是為了生成stub和skeleton——查看存放class文件的地方,多了如下兩個class文件:
q ServerImpl_Stub.class
q ServerImpl_Skel.class
這兩個類就是服務類生成的stub和skeleton??蛻舳顺绦蛎嫦蚪涌诰幊蹋蛻舳瞬糠中枰?/span>Server接口的class文件,還需要stub文件??蛻舳说脑创a如下:
public class RMIClient
{
//主方法,程序入口
public static void main(String[] args)throws Exception
{
//通過JNDI查找遠程服務
Server ser = (Server)Naming.lookup("rmi://:1099/fdf");
//調用遠程方法
System.out.println(ser.helloWorld("yeeku"));
//調用遠程方法。
System.out.println(ser.getPerson("yeeku",28));
}
}
從客戶端程序看,無法感受到Server的實現(xiàn)在遠端。程序調用Server實例方法時,與平常調用方法只有非常細微的區(qū)別:調用遠程方法必須拋出RemoteException。
再看遠程方法的返回值,helloWorld的返回值是String,而getPerson的返回值則是Person對象。遠程方法的返回值必須有一個要求:實現(xiàn)Serializable接口。因為遠程的方法的參數(shù)、返回值都必須在網絡上傳輸,網絡只能傳輸字節(jié)流,因此,要求參數(shù)、返回值都可以轉換成字節(jié)流——即實現(xiàn)序列化。主程序的如下一行代碼,用于注冊遠程服務端口:
LocateRegistry.createRegistry(1099);
1099是RMI服務的默認端口。然后執(zhí)行如下代碼綁定遠程服務
Naming.rebind("rmi://:1099/fdf", imp);
客戶端使用JNDI查找,查找遠程服務名。將遠程服務對象類型轉換成遠程接口類型,客戶端面向接口編程。RMI的具體實現(xiàn),依然是依賴于底層的Socket編程。RMI依賴于TCP/IP傳輸協(xié)議,服務器端skeleton建立ServerSocket監(jiān)聽請求,而客戶端建立Socket請求連接。RMI實現(xiàn)了網絡傳輸?shù)亩嗑€程、IO等底層細節(jié)。這些細節(jié)的實現(xiàn)就隱藏在rmic命令的執(zhí)行中:使用rmic命令編譯時生成的兩個class文件:
q stub:該文件用于與客戶端交流,建立Socket請求連接。
q skeleton:該文件用于與服務器端交流,建立ServerSocket監(jiān)聽請求。
RMI的原理示意如圖14.2所示。
圖14.2 RMI原理示意圖