RMI框架解析一
在實際應用中,爲了合理的分配軟硬件資源,會把各個對象分佈在不同的網絡節點上,這些對象之間能相互發送消息。
分佈式對象模型的實現系統應該具備以下功能:
1) 把分佈在不同節點上的對象之間發送的消息轉換爲字節序列,這一過程稱爲編組(marshalling)。
2) 通過套接字建立連接並且發送編組後的消息,即字節序列。
3) 處理網絡連接或傳輸消息時出現的各種故障。
4) 爲分佈在不同節點上的對象提供分佈式垃圾收集機制。
5) 爲遠程方法的調用提供安全檢查機制。
6) 服務器端運用多線程或非阻塞通信機制,確保遠程對象具有很好的併發性能,能同時爲多個客戶訪問。
7) 創建與特定問題領域相關的各種本地對象和遠程對象。
RMI框架封裝了所有底層通信細節,並且解決了編組,分佈式垃圾收集、安全檢查和併發性等通用問題。有了現成的框架,開發人員就只需專注開發也特定問題領域相關的各種本地對象和遠程對象。
RMI採用客戶/服務器通信方式。在服務器上部署了各種服務的遠程對象,客戶端請求訪問服務器上遠程對象的方法。
與特定問題領域相關的各種本地對象和遠程對象 |
分佈式對象模型的框架的接口 |
分佈式對象模型的框架的實現 |
Socket通信 |
TCP/IP網絡 |
第三方提供 |
負責開發特定軟件應用的開發人員創建 |
RMI框架採用代理來負責爲客戶與遠程對象之間通過Socket進行通信的細節。
RMI框架爲遠程對象分別生成了客戶端代理和服務器端代理。位於客戶端的代理類稱爲存根(Stub),位於服務器端的代理類稱爲骨架(Skeleton)。
客戶端 |
3.調用echo()方法
|
遠程對象 |
1.調用本地存根的echo()方法
遠程對象的的骨架 |
遠程對象的存根 |
4.發送被編組的返回值或異常
存根採用一種與平臺無關的編碼方式,把方法參數編碼爲字節序列,這個編碼過程爲參數編組。
RMI主要採用Java序列化機制參數編組。接着,存根把以下請求信息發送給服務器:
爲訪問的遠程對象的名字。
被調用的方法的描述
編組後的參數的字節碼序列
服務器端接收到客戶端的請求信息,然後由相應的骨架對象來處理這一請求信息,骨架對象執行以下操作:
反編組參數,即把參數的字節序列反編碼爲參數。
定位要訪問的遠程對象。
調用遠程對象的相應方法。
獲取方法調用產生的返回值或者異常,然後對它進行編組。
把編組後的換回值或者異常發送給客戶。
大致說來,創建一個RMI應用包括以下步驟:
1) 創建遠程接口:繼承java.rmi.Remote接口
2) 創建遠程類: 實現遠程接口
3) 創建服務器程序:負責在rmiregistry註冊表中註冊遠程對象。
4) 創建客戶程序:負責定位遠程對象,並且調用遠程對象的方法。
創建遠程接口:
遠程接口中聲明瞭可以被客戶程序訪問的遠程方法。RMI規範要求遠程對象所屬的類實現一個遠程接口,並且遠程接口符合以下條件:
1) 直接或間接繼承java.rmi.Remote接口。
2) 接口中所有方法聲明拋出java.rmi.RemoteException。
創建遠程類:
遠程類就是遠程對象所屬的類。RMI規範要求遠程類必須實現一個遠程接口。
此外,爲了使遠程類的實例變成能爲遠程客戶提供服務的遠程對象,可通過以下兩種途徑之一把它導出export爲遠程對象。
1) 使遠程類繼承java.rmi.server.UnicastRemoteObject類,並且遠程類的構造子拋出RemoteException。這也是最常用的方式。
2) 導出爲遠程對象的第二種方式:如果一個遠程類已經繼承了其他類,無法再繼承UnicastRemoteObject類,那麼可以在構造方法中調用UnicastRemoteObject類的靜態exportObject()方法,同樣,遠程類的構造方法也必須聲明爲拋出RemoteException。在其構造方法中調用UnicastRemoteObject.exportObject(this,0)方法,將自身導出爲遠程對象。
創建服務程序:
RMI採用一種命名服務機制來使得客戶程序可以找到服務器上的一個遠程對象。在JDK的安裝的bin子目錄相愛有一個rmiregistry.exe程序,它是提供命名服務的註冊表程序。
服務器程序的一大任務就是向rmiregistry註冊表註冊遠程對象。從JDK1.3API被整合到JNDI中。
在JNDI中,javax.naming.Context接口聲明瞭註冊、查找、以及註銷對象的方法。
bind(String name, Object obj),註冊對象,把對象與一個名字綁定。如果該名字已經與其它對象綁定,就會拋出NameAlreadyBoundException。
remind(String name, Object obj):註冊對象,把對象與一個名字綁定。如果該名字已經與其它對象綁定,不會拋出NameAlreadyBoundException,而是把氣昂前參數obj指定的對象覆蓋掉原先的對象。
lookup(String name):查找對象,返回與參數name指定的名字所綁定的對象。
unbind(String name):註銷對象,取消對象與名字的綁定。
遠程對象工廠設計模式:
rmiregistry註冊表只能用來註冊少量的遠程對象,已完成自舉服務。
如果把所有的遠程對象都註冊到rmiregistry註冊表,有以下缺點:
第一、 要保證每個遠程對象具有惟一的名字具有一定的難度。
第二、 不管客戶是否會訪問某個遠程對象,都必須事先創建它,有可能在服務器運行的生命週期中,有些遠程對象從來沒有被客戶訪問過。服務器上事先創建這些遠程對象,並且在註冊表中註冊它們,白白浪費了服務器的資源。
遠程對象工廠的設計模式。客戶程序從rmiregistry註冊表中找到一個負責創建和查找其它遠程對象的工廠對象,然後就可以由它來得到其它遠程對象,工廠對象本身當然也是遠程對象。
註冊表 |
創建或查找其它的遠程對象 |
工廠對象 |
客戶 |
遠程對象 |
遠程對象 |
package com.unmi.flight;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Flight extends Remote {
public String fly() throws RemoteException;
}
package com.unmi.flight;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class FlightImpl extends UnicastRemoteObject implements Flight {
private static final long serialVersionUID = 1L;
private String name;
public FlightImpl(String name) throws RemoteException {
this.name = name;
}
public String fly() throws RemoteException {
return "flight fly";
}
}
package com.unmi.flight;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface FlightFactory extends Remote {
public Flight getFlight(String name) throws RemoteException;
}
package com.unmi.flight;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Hashtable;
public class FlightFactoryImpl extends UnicastRemoteObject implements
FlightFactory {
protected Hashtable<String, Flight> flights; // 存放Flight對象的緩存
public FlightFactoryImpl() throws RemoteException {
flights = new Hashtable<String, Flight>();
}
public Flight getFlight(String name) throws RemoteException {
Flight flight = flights.get(name);
if (flight != null) return flight;
flight = new FlightImpl(name);
flights.put(name, flight);
return flight;
}
}
package com.unmi.flight;
import java.rmi.registry.LocateRegistry;
import javax.naming.InitialContext;
public class FlightServer {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
InitialContext ctx = new InitialContext();
ctx.bind("rmi:factory", new FlightFactoryImpl());
ctx.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.unmi.flight;
import javax.naming.InitialContext;
public class FlightClient {
public static void main(String[] args) {
try {
InitialContext ctx = new InitialContext();
FlightFactory factory = (FlightFactory)ctx.lookup("rmi:factory");
FlightFactory factory2 = (FlightFactory)ctx.lookup("rmi:factory");
System.out.println(factory == factory2);
Flight flight1 = (Flight)factory.getFlight("flight1");
Flight flight2 = (Flight)factory.getFlight("flight1");
System.out.println(flight1 == flight2);;
} catch (Exception e) {
e.printStackTrace();
}
}
}