單機 JVM 內方法調用:棧幀壓入、程序計數器跳轉、共享堆積物件。跨 JVM/跨機器調用:必須將調用語義 (對象、方法、參數) **序列化** 經網路傳輸,遠端**反序列化**執行,結果再序列化返回。RMI (Remote Method Invocation) 封裝了這套**編碼/解碼、網路通訊、服務發現**的樣板代碼,讓開發者像呼叫本地方法一樣呼叫遠端方法。
| 角色 | 職責 | 關鍵類/介面 |
|---|---|---|
| 遠端介面 | 定義可遠程調用的方法簽名,延伸 java.rmi.Remote,方法拋 RemoteException | 自定義介面 |
| 遠端實現 | 實際業務邏輯,繼承 UnicastRemoteObject (自動導出為遠端物件) | 自定義類 |
| Stub (客戶端存根) | 客戶端代理,持有遠端引用,負責參數編碼、網路發送、結果解碼 | rmic 自動生成 _Stub 類 |
| Skeleton (服務端骨架) | 服務端接收請求、解碼、分派調用真實實現、編碼返回 (JDK 1.2+ 內建於運行時,無需生成) | 運行時內建 |
| RMI Registry | 名稱服務,綁定「名稱 ↔ Stub」,客戶端查找獲取 Stub | LocateRegistry, Naming |
Client Stub (Client) Network Skeleton (Server) Remote Impl │ │ │ │ │ ├─ sayHello() ───► │ │ │ │ │ ├─ 編碼參數 ────────► │ │ │ │ │ ├─ TCP/JRMP ───────► │ │ │ │ │ ├─ 解碼、分派 ───────► │ │ │ │ │ ├─ 真實執行 │ │ │ │ ◄──── 返回值 ───────┤ │ │ │ ◄──── 編碼結果 ────┤ │ │ ├─ 解碼結果 ──────── │ │ │ │ ◄──── 返回值 ────┤ │ │ │
package com.demo.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloService extends Remote {
String sayHello(String name) throws RemoteException;
// 所有方法必須拋 RemoteException (或父類 IOException)
// 參數/返回值必須可序列化 (實現 Serializable 或為基本型別)
}
package com.demo.rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
protected HelloServiceImpl() throws RemoteException { super(); } // 必須拋 RemoteException
@Override
public String sayHello(String name) throws RemoteException {
return "Server says: Hello, " + name + "! (Time: " + new java.util.Date() + ")";
}
public static void main(String[] args) {
try {
// 1. 創建實現實例
HelloService service = new HelloServiceImpl();
// 2. 啟動 Registry (嵌入式,或單獨啟動 rmiregistry)
java.rmi.registry.LocateRegistry.createRegistry(1099);
// 3. 綁定名稱
java.rmi.Naming.rebind("rmi://localhost:1099/HelloService", service);
System.out.println("RMI 服務已啟動,綁定名稱: HelloService");
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.demo.rmi.client;
import com.demo.rmi.HelloService;
import java.rmi.Naming;
public class HelloClient {
public static void main(String[] args) {
try {
// 1. 查找 Stub (需類路徑包含 HelloService 介面、Stub 類)
HelloService service = (HelloService) Naming.lookup("rmi://localhost:1099/HelloService");
// 2. 調用如同本地方法
String resp = service.sayHello("Alice");
System.out.println("收到回應: " + resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Serializable 並顯式宣告 serialVersionUID,否則版本不一致導致 InvalidClassException。java.rmi.server.hostname 指定對外 IP,或使用 RMISocketFactory 固定端口範圍。rmiregistry 並配置高可用 (或遷移至 Zookeeper/Etcd/Consul 服務發現)。Jini 在 RMI 基礎上增加三大能力,解決傳統 RMI「強耦合 IP/端口/名稱」痛點:
| 特性 | 傳統 RMI | Jini |
|---|---|---|
| 服務發現 | 客戶端硬編碼 Registry IP + 服務名稱 | 自適應探索:組播/單播自動發現 Lookup Service,無需知 IP |
| 服務註冊 | 靜態綁定名稱字串 | 註冊 服務介面類型 + 序列化代理對象 (可為 Stub、完整實現、驅動程式) |
| 租約機制 | 無心跳,服務掛掉 Registry 仍保留條目 | 自恢復網絡:Lease 定期續租,失效自動移除,保證目錄準確性 |
| 代碼移動 | 客戶端預先部署 Stub | 服務可動態下載代理類 (Code Mobility),客戶端零部署 |
RMI (Java 專用、二進制協議 JRMP/IIOP、強耦合介面、單點 Registry)
│
▼
Web Services (SOAP/HTTP、XML、跨語言、WSDL 契約、ESB 集中治理)
│
▼
REST/JSON over HTTP (輕量、無狀態、標準化、API Gateway、服務網格)
│
▼
gRPC / Dubbo / Spring Cloud (高效二進制 Protobuf、服務發現、熔斷限流、可觀測、多語言)
RMI 是 Java 分布式計算的**起點**,展示了「遠程調用透明化」的經典模式:介面定義契約、Stub/Skeleton 代理模式、Registry 服務發現、序列化跨越邊界。雖然生產環境已被 gRPC、Dubbo、Spring Cloud 等現代框架取代,但理解 RMI 核心機制,對掌握**微服務通訊本質、序列化協議選型、服務發現原理、分布式異常處理**具有不可替代的教學價值。Jini 的租約、代碼移動、類型化發現思想,更直接啟發了現代服務網格的 Sidecar 模式與控制平面設計。