3.2.2 編譯源程序
首先將兩個接口類Compute.class和Task.class打成JAR包(compute.jar)傳到客戶端,或者向客戶端添加類路徑也行。這樣客戶端的類才能正確編譯。編譯過程不在詳述。
需要被其他JVM動態(tài)加載的類要能夠通過網絡遠程訪問到,因此可以將它們放在一個Web服務器上,通過HTTP訪問。當然在本機上測試時也可以使用文件路徑,在此使用文件路徑,如file:/c:\home\user\public_html\classes/
在本示例中注冊處需要動態(tài)從服務器端加載Compute.class,Task.class和ComputeEngine_Stub.class。
存根類ComputeEngine_Stub是通過rmic命令生成的,在Dos窗口中,到ComputeEngine所在目錄(注意包路徑不包含在內),運行
rmic engine. ComputeEngine 即可自動生成ComputeEngine_Stub.class。
注意此例中存根類是必須的,因為我們使用的是public static RemoteStub exportObject(Remote obj) throws RemoteException方法導出遠程對象的,如果采用其它方法則可以不需要,臨時動態(tài)生成動態(tài)代理(JDK5.0版本后)。
服務器端需要動態(tài)從客戶端加載類Pi.class。
客戶端需要動態(tài)從服務器端加載類ComputeEngine_Stub.class,而Compute.class和Task.class本地路徑中已有。
3.2.3 運行源程序
服務器端和客戶端在運行時都需要加載一個安全管理器,因此我們首先需要編寫兩個安全策略文件,允許訪問鏈接,這里使用最簡單的策略文件。
grant {
permission java.security.AllPermission;
};
其中客戶端策略文件client.policy和服務器端策略文件server.policy是相同的。
1. 啟動注冊表
在Dos窗口中運行 rmiregistry,此命令是JDK提供的。
2. 啟動服務器 (命令如下,Windows環(huán)境)
java -cp c:\home\ann\src;c:\home\ann\public_html\classes\compute.jar
-Djava.rmi.server.codebase=file:/c:\home\ann\public_html\classes\compute.jar
-Djava.rmi.server.hostname=localhost
-Djava.security.policy=server.policy
engine.ComputeEngine
java.rmi.server.codebase 指定了一個地址,所需要的類定義可以從此地址下載。
java.rmi.server.hostname 指定了存放遠程對象所對應存根對象的主機名稱,客戶端在嘗試遠程對象方法調用時將用到此地址。
java.security.policy 指定了所使用的安全策略文件。
3. 啟動客戶端 (命令如下,Windows環(huán)境)
java -cp c:\home\jones\src;c:\home\jones\public_html\classes\compute.jar
-Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/
-Djava.security.policy=client.policy
client.ComputePi localhost 45
運行后會得到如下結果:
3.141592653589793238462643383279502884197169399
下圖描述了在程序執(zhí)行過程中rmiregistry,ComputeEngine server,ComputePi client在哪里獲得類定義。

圖3.3 類加載圖
四、JDK5.0中的動態(tài)代理類
4.1 JDK5.0中RMI的新特性
如果是在JDK5.0下運行程序會發(fā)現(xiàn)一些有意思的事情,如不生成遠程對象的存根文件,程序在運行時不會報錯。仍然可以正確的運行得到結果(這一點困擾我很長時間)。這是因為在JDK5.0以前的版本中,需要用rmic命令來為遠程對象生成靜態(tài)的代理類(包括存根和骨架類),而在JDK5.0中,RMI框架會在運行時自動為遠程對象生成動態(tài)代理類(包括存根和骨架類),從而更徹底的封裝的RMI框架的實現(xiàn)細節(jié),簡化了RMI框架的使用方式。
在JDK5.0中,如果無法加載遠程對象的stub類或將系統(tǒng)屬性 java.rmi.server.ignoreStubClasses 設置為 "true",并且我們使用除public static RemoteStub exportObject(Remote obj) throws RemoteException方法外的其它重載方法導出遠程對象時,程序運行時會構造一個遠程對象的動態(tài)代理,是類java.lang.reflect.Proxy的一個實例,這樣在向注冊表注冊時傳遞給注冊表的是一個Proxy對象,而不是ComputeEngine_Stub對象,因為Proxy類在JDK中已有類定義,因此注冊處并不需要從服務器端加載Proxy類定義,但Compute類和Task類還是需要從服務器端加載的。
同樣的道理,客戶端從注冊處獲得Proxy動態(tài)代理對象后,也不需要從服務器端獲得其類定義,從而客戶端不會從服務器端動態(tài)加載任何類定義,也就是說客戶端的安全管理器可以省略。但服務器端仍然需要動態(tài)從客戶端加載Pi類定義,從而服務器端的安全管理器不能省略。
但是一旦生成了ComputeEngine_Stub存根類,那么程序就不會動態(tài)生成動態(tài)代理,那么整個運行過程就如3.2中所述。
4.2 JDK5.0中的動態(tài)代理類
動態(tài)代理類的字節(jié)碼在程序運行時由Java反射機制動態(tài)生成,無須程序員手工編寫它的源代碼。java.lang.reflect包中的Proxy類和InvocationHandler接口提供了生成動態(tài)代理類的能力。
Proxy 提供用于創(chuàng)建動態(tài)代理類和實例的靜態(tài)方法,它還是由這些方法創(chuàng)建的所有動態(tài)代理類的超類。
l public static Class<?> getProxyClass(ClassLoader loader,Class<?>[] interfaces) throws IllegalArgumentException 靜態(tài)方法負責創(chuàng)建動態(tài)代理類,并向其提供類加載器和接口數(shù)組。該代理類將由指定的類加載器定義,并將實現(xiàn)提供的所有接口。如果類加載器已經定義了具有相同排列接口的代理類,那么現(xiàn)有的代理類將被返回;否則,類加載器將動態(tài)生成并定義這些接口的代理類。
l public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 靜態(tài)方法返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。此方法相當于:Proxy.getProxyClass(loader, interfaces).getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] { handler });
由Proxy類的靜態(tài)方法創(chuàng)建的動態(tài)代理類(以下成為代理類)具有以下特點:
l 代理類是公共的、最終的,和非抽象類型的。
l 未指定代理類的非限定名稱。但是,以字符串 "$Proxy" 開頭的類名空間應該為代理類保留。
l 代理類繼承了 java.lang.reflect.Proxy類。
l 代理類會按同一順序準確地實現(xiàn)其創(chuàng)建時指定的接口。
l 如果代理類實現(xiàn)了非公共接口,那么它將在與該接口相同的包中定義。否則,代理類的包也是未指定的。注意,包密封將不阻止代理類在運行時在特定包中的成功定義,也不會阻止相同類加載器和帶有特定簽名的包所定義的類。
l 由于代理類將實現(xiàn)所有在其創(chuàng)建時指定的接口,所以對其 Class 對象調用 getInterfaces 將返回一個包含相同接口列表的數(shù)組(按其創(chuàng)建時指定的順序),對其 Class 對象調用 getMethods 將返回一個包括這些接口中所有方法的 Method 對象的數(shù)組,并且調用 getMethod 將會在代理接口中找到期望的一些方法。
l Proxy類的isProxyClass(Class<?> cl) 靜態(tài)方法可用來判斷參數(shù)指定的類是否為動態(tài)代理類。只有由 Proxy.getProxyClass 返回的類,或由 Proxy.newProxyInstance 返回的對象的類才是動態(tài)代理類。
l 代理類的 java.security.ProtectionDomain 與由引導類加載器(如 java.lang.Object)加載的系統(tǒng)類相同,原因是代理類的代碼由受信任的系統(tǒng)代碼生成。此保護域通常被授予 java.security.AllPermission。
l 每個代理類都有一個可以帶一個參數(shù)(接口 InvocationHandler 的實現(xiàn))的公共構造方法,用于設置代理實例的調用處理程序。并非必須使用反射 API 才能訪問公共構造方法,通過調用 Proxy.newInstance 方法(將調用 Proxy.getProxyClass 的操作和調用帶有調用處理程序的構造方法結合在一起)也可以創(chuàng)建代理實例。
l 每個代理類實例都和一個InvocationHandler實例關聯(lián)。Proxy類的getInvocationHandler(Object proxy)靜態(tài)方法返回與參數(shù)proxy指定的代理類實例所關聯(lián)的InvocationHandler對象。當調用代理類所實現(xiàn)接口的方法時,該方法會調用與它關聯(lián)的InvocationHandler對象的invoke()方法。
InvacationHandler接口為方法調用接口,它聲明了負責調用任意一個方法的invoke()方法:Object invoke(Object proxy, Method method, Object[] args) throws Throwable,參數(shù)proxy指定動態(tài)代理類實例,參數(shù)method指定被調用的方法,參數(shù)args指定向被調用方法傳遞的參數(shù),invoke方法的返回值表示被調用方法的返回值。
五、Java RMI總結
5.1 Java RMI的優(yōu)點
RMI機制給分布計算的系統(tǒng)設計、編程都帶來了極大的方便。只要按照RMI規(guī)則設計程序,可以不必再過問在RMI之下的網絡細節(jié)了,如:TCP和Socket等等。任意兩臺計算機之間的通訊完全由RMI負責。調用遠程計算機上的對象就像本地對象一樣方便。
1. 面向對象:
RMI可將完整的對象作為參數(shù)和返回值進行傳遞,而不僅僅是預定義的數(shù)據(jù)類型。也就是說,可以將類似Java哈西表這樣的復雜類型作為一個參數(shù)進行傳遞。
2. 可移動屬性:
RMI可將屬性從客戶機移動到服務器,或者從服務器移動到客戶機。
3. 設計方式:
對象傳遞功能使您可以在分布式計算中充分利用面向對象技術的強大功能,如二層和三層結構系統(tǒng)。如果用戶能夠傳遞屬性,那么就可以在自己的解決方案中使用面向對象的設計方式。所有面向對象的設計方式無不依靠不同的屬性來發(fā)揮功能,如果不能傳遞完整的對象,包括實現(xiàn)和類型,就會失去設計方式上所提供的優(yōu)點。
4. 安全性:
RMI使用Java內置的安全機制保證下載執(zhí)行程序時用戶系統(tǒng)的安全。RMI使用專門為保護系統(tǒng)免遭惡意小程序侵害而設計的安全管理程序。
5. 便于編寫和使用
RMI使得Java遠程服務程序和訪問這些服務程序的Java客戶程序的編寫工作變得輕松、簡單。遠程接口實際上就是Java接口。為了實現(xiàn)RMI的功能必須創(chuàng)建遠程對象任何可以被遠程調用的對象必須實現(xiàn)遠程接口。但遠程接口本身并不包含任何方法。因而需要創(chuàng)建一個新的接口來擴展遠程接口。新接口將包含所有可以遠程調用的方法。遠程對象必須實現(xiàn)這個新接口,由于新的接口擴展了遠程接口,實現(xiàn)了新接口,就滿足了遠程對象對實現(xiàn)遠程接口的要求,所實現(xiàn)的每個對象都將作為遠程對象引用。
5.2 Java RMI的缺點
RMI雖然編寫起來比較方便,但運行的效率較低,而且RMI局限于Java之間的通信,無法與其他的語言平臺進行通信。因此出現(xiàn)了RMI-IIOP技術,RMI-IIOP綜合了RMI的簡單性和CORBA的多語言性(兼容性),其次RMI-IIOP克服了RMI只能用于Java的缺點和CORBA的復雜性(可以不用掌握IDL)。
參考文獻
[1] 李華琰、郭英奎 著:Java 中間件開發(fā)技術 . 中國水利水電出版社,2005
[2] David Reilly , Michael Reilly著,沈鳳等譯.Java網絡編程與分布式計算.北京:機械工業(yè)出版社,2003
[3]孫衛(wèi)琴 著.Java網絡編程精解.北京:電子工業(yè)出版社,2007
[4] Trail RMI (The Java™ Tutorials).2007
http://java.sun.com/docs/books/tutorial/rmi/index.html
[5] JDK5.0 中文文檔
Author: orangelizq
email: orangelizq@163.com
posted on 2007-12-01 21:01
桔子汁 閱讀(1778)
評論(0) 編輯 收藏 所屬分類:
J2SE