【引言】
作為J2EE核心技術之一的RMI,它允許客服端調用一個遠端伺服器的元件,并傳回調用結果(傳回值或異常),可以完成分布式應用。整個調用過程由RMI實作,對使用者透明。
【Stub和Skeleton】
1、要了解RMI原理,Stub和Skeleton是必須先了解的兩個概念。
以下引用别的一段話:
做個比方說明這兩個概念。 假如你是A,你想借D的工具,但是又不認識D的管家C,是以你找來B來幫你,B認識C。B在這時就是一個代理,代理你的請求,依靠自己的話語去借。C呢他負責D家東西收回和借出 ,但是要有D的準許。在得到D的準許以後,C再把東西給B,B呢再轉給A。stub和skeleton在RMI中就是角色就是B和C,他們都是代理角色,在現實開發中隐藏系統和網絡的的差異, 這一部分的功能在RMI開發中對程式員是透明的。Stub為用戶端編碼遠端指令并把他們發送到伺服器。而Skeleton則是把遠端指令解碼,調用服務端的遠端對象的方法,把結果在編 碼發給stub,然後stub再解碼傳回調用結果給用戶端。所有的操作如下圖所示

Stub為遠端對象在用戶端的一個代理,當用戶端調用遠端對象的方法時,實際是委托Stub這個代理去調用遠端的對象,這個調用過程如下:
(1)初始化一個與遠端JVM的連接配接
(2)寫入并傳輸參數給遠端JVM
(3)執行遠端對象的方法調用,并等待調用結果的傳回(return)
(4)讀取調用的傳回值(也可能是一個異常)
(5)傳回調用的結果給調用者
這些操作(序列号參數, 建立Socket連接配接等等),都由這個Stub來透明化。
在遠端的JVM中,每一個對象(需要被遠端調用的對象)都有一個相應的skeleton(在Java2環境中,這個skeleton不是必須的,這個先不說),skeleton的作用是分發用戶端的調用到具體的實作類,skeleton接受 一個用戶端過來的調用過程如下:
(1)讀取用戶端傳遞過來的參數
(2)調用實作類的方法
(3)寫入并傳輸傳回結果給調用者,同樣的,這個結果也是函數調用結果或異常
2、Stub和Skeleton在什麼位置産生
Stub和Skeleton都是在伺服器産生的。
Stub存在于用戶端,作為用戶端的代理,讓我們總是認為用戶端産生了stub,接口沒有作用。實際上stub類是通過Java動态類下載下傳 機制下載下傳的,它是由服 務端産生,然後根據需要動态的加載到用戶端,如果下次再運作這個用戶端該存根類存在于classpath中,它就不需要再下載下傳了,而是直接加載。總的來說,stub是在服務端産生的,如果服務端的stub内容改變,那麼用戶端的也是需要同步更新。
【一個簡單的例子】
1、伺服器端
接口定義
package test.server;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IMyServer extends Remote {
public int add(int a, int b) throws RemoteException;
public int mul(int a, int b) throws RemoteException;
}
接口實作類
package test.server.impl;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import test.server.IMyServer;
public class MyServer extends UnicastRemoteObject implements IMyServer {
private static final long serialVersionUID = 1L;
public MyServer() throws RemoteException {
super();
}
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
@Override
public int mul(int a, int b) throws RemoteException {
return a * b;
}
}
用于測試的伺服器端
package test.server.impl;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ServerTest {
private static Registry registry = null;
public static Registry getInstance() throws RemoteException {
try {
return registry == null ? (registry = LocateRegistry
.createRegistry(1099)) : registry;
} catch (RemoteException e) {
throw e;
}
}
private ServerTest() {
}
public static void main(String[] args) throws InterruptedException {
try {
MyServer server = new MyServer();
getInstance().bind("server", server);
} catch (RemoteException e) {
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
System.err.println("server bind end");
}
}
2、客服端
首先客服端需要有遠端對象的接口IMyServer。
客服端測試類
package test.client;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import test.server.IMyServer;
public class ClientTest {
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
IMyServer server = (IMyServer) registry.lookup("server");
int a = 2, b = 10;
System.err.println(server.add(a, b));
System.err.println(server.mul(a, b));
} catch (RemoteException e) {
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
}
}
3、運作
(1)先運作伺服器端的ServerTest
(2)運作用戶端的ClientTest,輸出12 20
【分析】
1、首先,遠端對象和調用傳遞的參數都必須實作接口Serializable接口,關系系列化與反系列化可以參看我的另一篇文章 《Java Serializable系列化與反系列化》
2、MyServer繼承UnicastRemoteObject的作用與好處
先來看看UnicastRemoteObject這個對象的結構,如下圖:
(1)先來看看java.rmi.server.RemoteObject這個類,首先它實作了Serializable, Remote這兩個接口
RemoteObject的真正作用在于,他重寫了Object的hashCode、equals、和toString方法,重寫hashCode方法使得遠端對象可以存放在Hashtable等一些哈希結果的集合中,重寫equals方法使得遠端對象可以進行比較,而重寫後的toString方法傳回遠端對象的描述串。該類還實作專門的(私用)方法writeObject 和方法readObject,用于系列化與反系列化。
(2)java.rmi.server.RemoteServer類
在這個類中,則提供了一些對調試非常有用的方法,它一共有3個方法:
static String getClientHost():傳回一個客戶機主機的字元串表示形式,用于在目前線程中正在處理的遠端方法調用。
static PrintStream getLog():傳回用于 RMI 調用日志的流。
static void setLog(OutputStream out):将 RMI 調用記錄到輸出流 out 中。
同時要注意,該類是abstract的。
(3)java.rmi.server.UnicastRemoteObject類
再看看這個類,通常,遠端對象都 繼承UnicastRemoteObject類,UnicastRemoteObject 類提供遠端對象所需的基本行為。在這個類中提供了支援建立和導出遠端對象的一系列方法,一個對象繼承UnicastRemoteObject它将獲得以下特性:
A、對這種對象的引用至多僅在建立該遠端對象的程序生命期内有效
B、使得遠端對象既有使用TCP協定通信的能力(Socket)
C、對于用戶端與伺服器的調用、傳參、傳回值等操作使用流的方式來處理
其他的,java.rmi.registry.LocateRegistry類提供了一系列的方法用于建立、擷取Registry執行個體的方法;在Registry接口中,定義了一系列的方法,用于操作遠端對象包括:綁定對象(bind)、擷取對象(lookup)、重寫綁定(rebind)、解除綁定(unbind)和傳回系統資料庫綁定清單(list)方法(也可以使用java.rmi.Naming來操作)。
剛學習RMI,不對的地方歡迎指出