Java RMI (Remote Method Invocation) 讓分布在不同 JVM (甚至不同物理機) 的 Java 對象能像本地調用一樣透明地相互調用方法。本文系統講解 RMI 核心架構 (遠程接口、實現類、Stub/Skeleton、註冊表、序列化)、完整服務端/客戶端代碼實現、安全管理器與類加載機制。
應用層: 遠程接口 (extends Remote) <---> 遠程對象實現 (extends UnicastRemoteObject)
↑ ↑
代理層: Stub (客戶端代理) Skeleton (服務端分發器) [JDK 1.2+ 內置於 Stub]
↑ ↑
傳輸層: JRMP (Java Remote Method Protocol) / IIOP (RMI-IIOP)
↑ ↑
基礎層: Java 序列化 (Object Serialization) + TCP/IP
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface TimeKeeper extends Remote {
// 1. 必須拋出 RemoteException (網絡/遠程異常)
// 2. 參數/返回值: 基本類型、Serializable、Remote 接口
public String currentDate() throws RemoteException;
public String currentTime() throws RemoteException;
// 示例: 傳遞可序列化對象
public void logEvent(LogEntry entry) throws RemoteException; // LogEntry implements Serializable
// 示例: 返回遠程對象引用
public DataService getDataService() throws RemoteException; // DataService extends Remote
}
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class TimeKeeperImpl extends UnicastRemoteObject implements TimeKeeper {
// 1. 必須提供拋出 RemoteException 的無參構造器
// 2. super() 會自動導出對象 (監聽端口,準備接收調用)
public TimeKeeperImpl() throws RemoteException {
super(); // 導出對象,綁定匿名端口
}
// 也可指定端口: super(1099) 或 super(0, csf, ssf) 自定義 Socket 工廠
@Override
public String currentDate() throws RemoteException {
Calendar cal = new GregorianCalendar();
return (cal.get(Calendar.MONTH) + 1) + "/" +
cal.get(Calendar.DAY_OF_MONTH) + "/" +
cal.get(Calendar.YEAR);
}
@Override
public String currentTime() throws RemoteException {
Calendar cal = new GregorianCalendar();
return String.format("%02d:%02d:%02d",
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
cal.get(Calendar.SECOND));
}
}
註冊表 (Registry) 運行在默認端口 1099,將字符串名稱綁定到遠程對象引用 (Stub)。
Naming.bind(name, remoteObj):綁定,名稱已存在拋異常。Naming.rebind(name, remoteObj):重新綁定,覆蓋舊綁定。Naming.unbind(name):解除綁定。Naming.lookup(name):查找返回 Stub 引用。Naming.list():列出所有綁定名稱。名稱格式://host:port/name (host/port 可省略默認 localhost:1099)。
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class RMIServer {
public static void main(String[] args) {
// 1. 安裝安全管理器 (關鍵!允許動態下載類)
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
// 2. 創建並導出遠程對象
TimeKeeperImpl timeKeeper = new TimeKeeperImpl();
// 3. 確保註冊表運行 (進程內啟動,避免單獨啟動 rmiregistry)
// LocateRegistry.createRegistry(1099); // 默認端口
// 4. 綁定到註冊表
String serviceName = "//localhost/TimeKeeper"; // 或 //192.168.1.100/TimeKeeper
Naming.bind(serviceName, timeKeeper);
System.out.println("TimeKeeper 服務已啟動,綁定至: " + serviceName);
System.out.println("按 Ctrl+C 停止服務...");
// 主線程保持運行
Thread.currentThread().join();
} catch (Exception e) {
System.err.println("服務啟動失敗: " + e.getMessage());
e.printStackTrace();
}
}
}
import java.rmi.Naming;
import java.rmi.RemoteException;
public class RMIClient {
public static void main(String[] args) {
try {
// 1. 從註冊表查找遠程對象 Stub
String serviceName = "//localhost/TimeKeeper"; // 與服務端綁定名一致
TimeKeeper timeKeeper = (TimeKeeper) Naming.lookup(serviceName);
// 2. 像本地調用一樣調用遠程方法
System.out.println("遠程日期: " + timeKeeper.currentDate());
System.out.println("遠程時間: " + timeKeeper.currentTime());
} catch (Exception e) {
System.err.println("客戶端調用失敗: " + e.getMessage());
e.printStackTrace();
}
}
}
rmic -v1.2 TimeKeeperImpl (生成 TimeKeeperImpl_Stub.class)。**現代 JDK (5+) 運行時動態生成 Stub (動態代理),無需預編譯 rmic**,但部署到舊容器/特定環境仍需預生成。| 參數類型 | 傳遞方式 | 說明 |
|---|---|---|
| 基本類型 (int, long...) | 按值 | 直接複製字節 |
實現 Serializable | 按值 (深度複製) | 對象圖完整序列化傳輸,**遠程端操作副本不影響源端** |
實現 Remote | 按引用 | 傳遞 Stub 引用,**遠程端調用方法實際路由回源端對象** |
| 既 Serializable 又 Remote | 按引用 (Remote 優先) | 傳遞 Stub |
-Djava.rmi.server.codebase=https://host:port/classes/ (需 HTTP 服務器托管 JAR)。// policy 文件 (server.policy)
grant {
permission java.security.AllPermission; // 開發測試用,生產嚴格限制
// 生產示例:
// permission java.net.SocketPermission "host:1099", "accept,connect";
// permission java.util.PropertyPermission "java.rmi.server.codebase", "read";
// permission java.io.FilePermission "/path/to/classes/-", "read";
};
啟動命令:java -Djava.security.policy=server.policy -Djava.rmi.server.codebase=https://localhost:8080/classes/ RMIServer
rmiregistry 或代碼 LocateRegistry.createRegistry)、端口被佔用、防火牆攔截。serialVersionUID)。RMI 通過 遠程接口 + UnicastRemoteObject + 註冊表 + JRMP + 序列化 構建了純 Java 的透明分布式對象模型。理解 Stub/Skeleton 代理模式、序列化語義、動態類加載、安全管理器機制,不僅能構建 RMI 應用,更是掌握 EJB、Jini、JMX、分布式 Spring (RMI 導出器) 等上層框架的內功心法。