1.RMI和Java Remote Method Invocation
2.1 調用本地對象
首先我們需要定義一個Java類,該類有一個方法localCall。然後,我們寫一個類用於調用這個本地對象。
- package com.ztesoft.provisioning.local;
- /**
- * 本地對象,用於被調用
- * @author Lethorld
- *
- */
- public class LocalObject {
- /**
- * 本地調用,用於測試
- * @param arg 入參
- * @return 返回
- */
- public String localCall(String arg) {
- // TODO : do something.
- return arg;
- }
- }
這樣我們便完成了本地的一次對象調用。我們的運行main函數後,主程序會new一個LocalObject對象,並且調用該對象的localCall方法。localCall方法根據入參"Hello",返回相應的返回值。這裏面沒有做處理,所以返回的是Hello。
- package com.ztesoft.provisioning.local;
- /**
- * 本地調用者
- * @author Lethorld
- *
- */
- public class LocalInvoker {
- /**
- * 主函數
- * @param args 入參
- */
- public static void main(String[] args) {
- LocalObject object = new LocalObject();
- String retn = object.localCall("Hello");
- System.out.println(retn);
- }
- }
OK,這段代碼應該是所有隻要稍微懂一點Java的人都能夠看明白,並且很輕鬆地寫出來的。那麼,對於Java的遠程調用,也就是上面所說的RMI,是否可以簡單的把所有的local改成remote就OK了呢?答案是否定的。2.2 定義與實現分離
要實現遠程調用,首先必須考慮的是如何將方法的定義和方法的實現分離開。如果方法定義與實現不能相互分離,那麼我們怎麼能夠將方法的實現放到服務器端,而僅僅在客戶端上保存一個方法的聲明呢?對於Java,要完成將定義與實現相分離的任務,我們自然而然地想到了接口(interface)這個工具。請看百度百科中對於Java接口的描述:“Java中的接口是一系列方法的聲明,是一些方法特徵的集合,一個接口只有方法的特徵沒有方法的實現,因此這些方法可以在不同的地方被不同的類實現,而這些實現可以具有不同的行爲(功能)。”你看,這難道不就是我們想要的所謂的定義與實現相分離的特性嗎?好吧,下面我們用接口來重寫上面的例子。首先我們定以接口,接口的定義這裏不贅述,也不屬於本文準備詳細闡述的內容。這個接口的名字叫ILocalObject,它有一個方法的聲明叫localCall。注意localCall只是一個聲明,它沒有顯示它將會如何實現localCall這個操作。然後我們定義一個類LocalObjectImpl,該類實現了接口ILocalObject。方法localCall和2.1中介紹的完全一樣。
- package com.ztesoft.provisioning.local;
- /**
- * 本地調用接口
- * @author Lethorld
- *
- */
- public interface ILocalObject {
- /**
- * 本地調用,用於測試
- * @param arg 入參
- * @return 返回
- */
- public String localCall(String arg);
- }
- package com.ztesoft.provisioning.local.impl;
- import com.ztesoft.provisioning.local.ILocalObject;
- /**
- * 接口的實現類
- * @author Lethorld
- *
- */
- public class LocalObjectImpl implements ILocalObject {
- public String localCall(String arg) {
- // TODO : do something.
- return arg;
- }
- }
現在我們要改一下我們的主函數,用接口的方式完成對方法的調用。下面代碼的17行,我們定義了一個接口對象object,它指向一個實現了該接口的類LocalObjectImpl的實例。
- package com.ztesoft.provisioning.local.impl;
- import com.ztesoft.provisioning.local.ILocalObject;
- /**
- * 接口的實現類
- * @author Lethorld
- *
- */
- public class LocalObjectImpl implements ILocalObject {
- public String localCall(String arg) {
- // TODO : do something.
- return arg;
- }
- }
- package com.ztesoft.provisioning.local;
- import com.ztesoft.provisioning.local.impl.LocalObjectImpl;
- /**
- * 本地調用者
- * @author Lethorld
- *
- */
- public class LocalInvoker {
- /**
- * 主函數
- * @param args 入參
- */
- public static void main(String[] args) {
- ILocalObject object = new LocalObjectImpl();
- String retn = object.localCall("Hello");
- System.out.println(retn);
- }
- }
2.3 萬事俱備!讓我們開始向RMI進發。
有了2.1和2.2的鋪墊,現在我們可以嘗試寫一個由JRMI實現的遠程調用的程序了。首先,我們定義一個接口,該接口要繼承自Remote接口。Remote接口在jdk中的解釋爲:“用於標識其方法可以從非本地虛擬機上調用的接口。”其實原理很簡單,我們要使用遠程調用的特性,那麼註定我們的類和簡單的本地類之間的處理是不完全一樣的。比如說:怎麼遠程通信?怎麼序列化(反序列化)入參、返回值?對於這些差異的處理,要麼我們用戶自己實現,要麼丟給RMI,由RMI框架幫我們實現。當然,作爲用戶,我們不希望每次寫個遠程調用都去處理這些共性的東西。於是,RMI框架抽象出來一個Remote接口。這樣,對於Java程序來說,凡是遇到繼承自Remote的接口,Java編譯器就知道這個類可能會被遠程調用了。其二,所有的遠程調用的接口的方法聲明中,必須含有RemoteException的聲明。從JDK文檔中可以看出來,RemoteException是繼承自IOException。這是什麼意思呢?因爲我們遠程調用是通過網絡實現的,所以對於服務器上的異常,客戶端可以統一認爲是IO讀寫異常。這也就不難理解爲什麼RemoteException會被定義成IOException的子類了。這裏的處理個人理解可能有點泛化的意味在裏面。遠程調用,在服務器上(或者在通訊過程中)可能會發生異常的錯誤,這點所有人都可以保證。而反過來說,沒有人可以保證遠程調用,在服務器上(或者在通訊過程中)永遠不發生錯誤。既然這樣,那我們就規定:所有繼承Remote的接口的方法聲明中,必須含有RemoteException的聲明。下面是一個繼承自Remote接口的接口,接口取名爲IRemoteTest。IRemoteTest內部有個remoteTest的方法的聲明。這個方法接受一個String類型的入參,返回值的類型也是String的。同時,它需要申明可能拋出RemoteException異常。接口定義好了,我們可以先把客戶端的調用程序寫完(接口存在的好處之一就是在不知道方法如何實現的前提下,我們同樣可以完成對該方法調用的後續操作)。對於2.2中的調用,我們用一句
- package com.ztesoft.provisioning.rmiserver;
- import java.rmi.Remote;
- import java.rmi.RemoteException;
- /**
- * 遠程調用RMI,定義的接口
- * @author Lethorld
- * Remote 接口用於標識其方法可以從非本地虛擬機上調用的接口。
- * 任何遠程對象都必須直接或間接實現此接口。只有在“遠程接口”
- *(擴展 java.rmi.Remote 的接口)中指定的這些方法纔可遠程使用。
- * 實現類可以實現任意數量的遠程接口,並且可以擴展其他遠程實現類。
- * RMI 提供一些遠程對象實現可以擴展的有用類,這些類便於遠程對象創建。
- * 這些類是 java.rmi.server.UnicastRemoteObject
- * 和 java.rmi.activation.Activatable。
- */
- public interface IRemoteTest extends Remote{
- public String remoteTest(String name) throws RemoteException;
- }
便可以實現創建一個LocalObjectImpl對象,並把這個對象賦給ILocalObject接口的任務。但是遠程調用不行,因爲你無法確切地知道遠程服務器上的對象的名稱(它並不是你本地的某個java類,它不受你的管束)。我們需要一個新的機制可以幫助我們獲得遠程對象的引用。這部分的工作有類Naming類實現。JDK中對該類的描述爲:“
- ILocalObject object = new LocalObjectImpl();
Naming
類提供在對象註冊表中存儲和獲得遠程對遠程對象引用的方法。”那麼如何去定位一個遠程對象呢?換句話說,我們如何唯一標識一個遠程對象?讓我們回到通常的WEB程序來看看究竟該如何處理。通常,我們要訪問某一個網站,我們會在瀏覽器中輸入網址。比如我們要訪問csdn網站,我們可以輸入http://www.csdn.net,點擊確定,於是我們就可以瀏覽csdn的主頁了。同樣,我們還可以輸入http://117.79.93.196來訪問csdn主頁。一般情況下,一個網站的地址會是這樣的結構:http://ip:port。http://標誌協議名,ip:port是典型的tcp地址。這樣我們便能定位到某臺機器上的某個特定的發佈了的程序了。同理在瀏覽器中輸入http://write.blog.csdn.net/postedit/7089374,你便會進入csdn博客的某篇文章(其實就是本文啦),也就是能訪問到csdn網站的某個子資源。於是,我們可以認爲,對於web應用http://ip:port/sub1/subsub1/...可以定位一個唯一的資源。對於RMI,其實貌似也是這麼做的,只不過RMI不是HTTP,所以不用HTTP://開頭;RMI貌似也不需要使用sub1/subsub1這樣繁雜的子層嵌套結構。故同理:一個rmi://ip:port/name可以唯一定位一個RMI服務器上的發佈了的對象。那麼事實是不是這樣呢?請看JDK關於Naming類的介紹,你會驚奇的發現,世界上的事,那都是想通的~
那麼,剩下的就太簡單了:
- package test.com.ztesoft.zsmart.bss.provisioning.serviceActivation;
- import java.net.MalformedURLException;
- import java.rmi.Naming;
- import java.rmi.NotBoundException;
- import java.rmi.RemoteException;
- import com.ztesoft.provisioning.rmiserver.IRemoteTest;
- public class RmiSynCallTest {
- public static void main(String[] args) throws RemoteException, MalformedURLException, NotBoundException {
- String url = "rmi://localhost:9527/rmitest";
- IRemoteTest test = (IRemoteTest)Naming.lookup(url);
- System.out.println(test.remoteTest("Lethorld"));
- }
- }