java分布式对象——远程方法中的参数和返回值+远程对象激活
【0】README
1)本文文字描述轉自 core java volume 2, 旨在學習 java分布式對象——遠程方法中的參數和返回值+遠程對象激活 的相關知識;
【1】遠程方法中的參數和返回值
1) 在開始進行遠程方法調用時,調用參數需要從客戶端的虛擬機中移動到服務器的虛擬機中。
2) 對于從一個虛擬機向另一個虛擬機傳值,我們將其區分成兩種情況:傳遞遠程對象和傳遞非遠程對象。
【1.1】傳遞遠程對象
1)當一個對遠程對象的引用從一個虛擬機傳遞到另一個虛擬機時,該遠程對象的發送者和接收者都將持有一個對同一個實體的引用。這個引用并非是一個內存位置(內存位置在單個虛擬機內才有意義),而是由網絡地址和該遠程對象的唯一標識符構成的。這個信息給封裝在存根對象中。
- 1.1) 需要始終牢記的是: 在遠程引用上的方法調用明顯比在本地引用上的方法調用執行得慢,并且潛在地也更不可靠。
【1.2】傳遞非遠程對象
Conclusion)總結一下,在虛擬機之間傳遞值有兩種機制:
- c1)實現了Remote接口: 的類的對象將作為遠程引用傳遞;
- c2)實現了Serializable接口: 但是沒有實現Remote接口的類的對象將使用序列化進行復制;
Attention)
- A1)這兩種機制都是自動化的,而且不需要任何程序員的干預。
- A2)請記住,序列化對于大型對象來說會比較慢,而且遠程方法不能改變被序列化的參數。
- A3)當然,你可以通過傳遞遠程引用來避免這些問題。但是這么做代價太大: 在遠程引用上調用方法與調用本地方法相比,代價高得多。
- A4)意識到這些開銷有助于在設計遠程服務時進行選擇。
1)看個荔枝: 我們的下一個示例程序將展示遠程和可序列化對象的傳遞。我們將Warehouse接口修改為代碼11-5的樣子,如果給定關鍵詞列表,這個倉庫將返回最佳匹配的Product。
public interface Warehouse extends Remote
{
double getPrice(String description) throws RemoteException;
Product getProduct(List keywords) throws RemoteException;
} // getProduct方法的參數具有List類型。因此,參數值必須屬于實現了List接口的可序列化的類,例如ArrayList。
Attention) Product類是可序列化的,服務器構建了一個Product對象,而客戶端獲取了它的一個副本(參見圖11-7)。
【1.3】動態類加載
1)應用背景:關鍵詞列表發送到了服務器,而且倉庫也返回了Product類的一個實例。當然,客戶端程序在編譯時需要Product.class類文件。但是,只要我們的服務器程序無法找到關鍵詞的匹配,它就會返回一件肯定讓每個人都很高興的產品:《Java核心技術》這本書。這個對象是Book類的實例,而Book類是Product的子類。
2)problem+solution:
- 2.1)problem: 當客戶端編譯時,它也許從未看到過Book類。但是在運行時,它需要能夠執行覆蓋了Product方法的Book方法,這說明客戶端需要擁有在運行時加載額外類的能力。
- 2.2)solution: 客戶端使用了與RMI注冊表相同的機制,即類由web服務器提供服務,RMI服務器類將URL傳遞給客戶端,客戶端創建要求下載這些類的HTTP請求。
3)problem+solution:
- 3.1)problem: 只要一個程序從網絡位置加載新的代碼,就會涉及安全問題。
- 3.2)solution:正是由于這個原因,我們需要在動態加載類的RMI應用中使用安全管理器。
4) 安裝安全管理器:使用RMI的程序應該安裝一個安全管理器,去控制動態加載類的行為。可以用下面的指令安裝:
System.setSecurityManager(new SecurityManager());
5)problem+solution:
5.1)problem: 默認情況下,SecurityManager會限制程序中所有的代碼去建立網絡連接,但是,我們的程序需要建立到三個遠程位置的網絡連接(connection):
- c1)加載遠程類的Web服務器;
- c2) RMI注冊表;
- c3)遠程對象;
5.2)solution:為了允許這些操作,需要提供一個策略文件。
- 5.3)看個荔枝: 下面的策略文件允許一個應用建立任何到端口號至少為1024的某個端口的網絡連接。(RMI端口默認為1099,遠程對象使用的端口也大于等于1024。我們使用8080端口來下載類。)
grant
{
permission java.net.SocketPermission
“*:1024-65535”, “connect”;
};
6)需要通過將java.security.policy屬性設置為這個策略文件名,來指示安全管理器去讀取該策略文件。
6.1)可以使用下面這個調用:
System.setProperty(“java.security.policy”, “rmi.policy”);
6.2)或者,可以在命令行指定系統屬性設置:
-Djava.security.policy=rmi.policy
7)為了運行示范程序,請確保已經關閉了在前面的示范程序中啟動的RMI注冊表、Web服務器和服務器程序。打開四個控制臺窗口,然后執行下面的步驟:
- 7.1) 編譯接口、實現、客戶端和服務器類的源文件。
javac *.java 7.2) 創建三個目錄:client、server和download,然后按下面這樣組裝它們:
client/
WarehouseClient.class
Warehouse.class
Product.class
client.policy
server/
Warehouse.class
Product.class
Book.class
WarehouseImpl.class
WarehouseServer.class
server.policy
download/
Warehouse.class
Product.class
Book.class7.3) 在第一個控制臺窗口中,轉到download目錄下,啟動NanoHTTP
- 7.4) 在第二個控制臺窗口中,轉到server目錄下,啟動服務器: java -Djava.rmi.server.codebase=http://localhost:8080/ WarehouseServer
- 7.5) 在第三個控制臺窗口中,轉到client目錄下,運行客戶端:
8)看個荔枝:
- 遠程對象傳送全部源代碼參見:https://github.com/pacosonTang/core-java-volume/tree/master/coreJavaAdvanced/chapter11/rmi_transfer
【1.4】具有多重接口的遠程引用(遠程類實現多個接口)
1)可以使用instanceof操作符來查看一個特定的遠程對象是否實現了某個接口。假設我們通過一個類型為Warehouse的變量得到了一個遠程對象:
Warehouse location = product.getLocation();
2)這個遠程對象可能是一個服務中心,但也可能不是。要確定到底是不是,可以使用下面的測試:
if (location instanceof ServiceCenter)
3)如果測試通過,就可以將location轉型為ServiceCenter,并調用getReturnAuthorization方法。
【1.5】遠程對象與 equals,hashCode 和 clone 方法
1) 插入集中的對象必須覆寫equals方法。如果是散列集或散列映射表,則需定義hashCode方法。
2)不能在遠程接口中定義equals方法 和 hashCode 方法:然而,比較遠程對象時有一個問題。如果要比較兩個遠程對象是否具有相同的內容,調用equals則必須聯系包含這些對象的服務器,然后比較它們的內容。而該調用可能會失敗。但是,Object類中的equals方法并未聲明會拋出RemoteException異常,而遠程接口中的所有方法都必須拋出該異常。因為子類的方法不能比它覆寫的父類的方法拋出更多的異常,所以不能在遠程接口中定義equals方法。hashCode也是這樣。
3)存根對象的equals 方法 和 hashCode 方法: 相反,存根對象的equals和hashCode方法只是查看遠程對象的位置。只要它們指向相同的遠程對象,equals方法就認為兩個存根相同。
Conclusion)總之,可以使用集與散列表中的遠程引用,但是必須記住,對它們進行等價測試以及散列計算并不會考慮遠程對象的內容。你不能直接克隆遠程引用。
【2】 遠程對象激活(延遲構造遠程對象)
1)激活機制: 激活機制(activation)允許延遲構造遠程對象,僅當至少有一個客戶端調用遠程對象上的遠程方法時,才真正去構造該遠程對象。 (干貨——激活機制定義)
2)引用激活機制的client 和 server:
- 2.1)client:要享用激活機制的好處,客戶端代碼完全無需改動。客戶端只是簡單的請求一個遠程引用,然后通過它進行調用而已。
- 2.2)server:然而,服務器程序則需由一個激活程序來代替。該程序構造了對象的激活描述符(activation descriptors),這樣的對象可以延遲構造,并且該程序通過命名服務為遠程方法調用綁定了接收者。第一次對這樣的對象進行方法調用時,激活描述符中的信息將會用來構造該對象。
3)應用激活機制的遠程對象: 必須繼承Activatable類而不是UnicastRemoteObject類,當然,還需實現一個或多個遠程接口。例如,
class WarehouseImpl
extends Activatable
implements Warehouse
{
…
}
4)因為對象的構造是延遲進行的,所以它必須以標準方式實現。因此,構造器必須包含以下兩個參數(params):
- p1)一個激活ID(只需傳遞給父類的構造器)
p2)一個包含所有構造信息的對象,包裝為MarshalledObject;
4.1)在構建激活描述符時,需要像下面這樣從構造信息中構造一個MarshalledObject:
MarshalledObject param = new MarshalledObject(constructionInfo);- 4.2)在實現對象的構造器中,使用MarshalledObject類的get方法來獲得反序列化之后的構造信息:
T constructionInfo = param.get();
5)看個荔枝:為了演示激活,我們修改了WarehouseImpl類,使得構造信息是一個由描述和價格構成的映射表。這個信息被包裝到了MarshalledObject中,并且在構造器中被拆包出來:
public WarehouseImpl(ActivationID id, MarshalledObject<Map<String, Double>> param) throws RemoteException, ClassNotFoundException, IOException { super(id, 0); prices = param.get(); System.out.println("Warehouse implementation constructed."); }- 5.1)將0作為父類構造器的第二個參數: 代表RMI類庫應該分配一個合適的端口號作為監聽端口。構造器會打印一個信息,這樣就可以看到所需的產品對象已經被激活了。
- Attention) 其實遠程對象不是一定要繼承Activatable類,例如,可以將下面的靜態方法調用放在服務器類的構造器中。
Activatable.exportObject(this, id, 0)
6)如何構建激活程序
- step1)需要定義一個激活組。一個激活組描述了啟動遠程對象所在的虛擬機所需的公共參數,其中最重要的參數是安全策略。
step2) 然后如下構造一個激活組描述符:
Properties props = new Properties();
props.put(“java.security.policy”, “/server/server.policy”);
ActivationGroupDesc group = new ActivationGroupDesc(props, null);
//第二個參數描述了一個特殊的命令選項,在這個例子中不需要任何選項,所以我們傳遞了一個null引用。step3)創建一個組ID
ActivationGroupID id = ActivationGroup.getSystem().registerGroup(groupstep4) 構造一個激活描述符了。對于需要構造的每一個對象,都應該包括以下內容(contents):
- c1)激活組ID,對象將在與其對應的虛擬機上被構造;
- c2)類的名字(例如”ProductImpl”或”com.mycompany.MyClassImpl”);
- c3) URL字符串,由該URL加載類文件。這應該是基本URL,不含包的路徑;
- c4)編組后的構造信息。
7)看個荔枝:
MarshalledObject param = new MarshalledObject(constructionInfo);
ActivationDesc desc = new ActivationDesc(id, “WarehouseImpl”,
“http://myserver.com/download/“, param);
7.1)將此描述符傳遞給靜態的Activatable.register方法。它返回一個對象,該對象實現了實現類的遠程接口。可以使用命名服務綁定該對象:
Warehouse centralWarehouse = (Warehouse) Activatable.register(desc);
namingContext.bind(“rmi:central_warehouse”, centralWarehouse);Attention) 與前例的服務器程序不同,激活程序在注冊與綁定激活接收者之后就會退出。僅當遠程方法調用第一次發生時,才會構造遠程對象。
8)荔枝:
8.1)啟動遠程對象激活程序,可按一下steps 進行:
- step1) 編譯所有文件;
step2)發布類文件:
client/
????????WarehouseClient.class
????????Warehouse.class
server/
???????? WarehouseActivator.class
???????? Warehouse.class
???????? WarehouseImpl.class
???????? server.policy
download/
???????? Warehouse.class
???????? NanoHTTPD.java
rmi/
???????? rmid.policystep3)啟動RMI注冊表
step4)在rmi目錄中啟動 RMI激活守護程序
rmid -J-Djava.security.policy=rmid.policy
rmid程序監聽激活請求,并且激活另一個虛擬機中的對象。要啟動一個虛擬機,rmid 程序需要一定的權限。這些權限都在一個策略文件中說明了。使用-J, 可以傳遞一個選項給運行激活守護程序的虛擬機;
step5)在download 目錄中啟動NanoHTTPD Web 服務器;
step6) 從server目錄運行激活程序
java -Djava.rmi.server.codebase=http://localhost:8080/ WarehouseActivator
- step7) 從client目錄運行客戶端程序;
Attention) 上述結果是可以正確查找遠程對象,但是當調用Warehouse接口的實現類的getPrice 方法的時候,卻拋出了異常,搞了一下午,我確實無能為力了,先留在這里,以后有機會再來解決。。
- 相關命令行指令如下:
- for full source code, please visit https://github.com/pacosonTang/core-java-volume/tree/master/coreJavaAdvanced/chapter11/activation
總結
以上是生活随笔為你收集整理的java分布式对象——远程方法中的参数和返回值+远程对象激活的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java分布式对象RMI应用测试用例
- 下一篇: 寻找伊朗历史上第一款电子游戏