首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java RMI 實現分布式計算核心原理與完整代碼實戰:遠程接口、Stub/Skeleton、註冊表、序列化

     編輯:Java 相關知識

RMI:Java 原生分布式對象模型的基石

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));
    }
}

RMI 註冊表:命名服務

註冊表 (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)。

RMI 服務端完整啟動類

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();
        }
    }
}

RMI 客戶端調用代碼

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();
        }
    }
}

關鍵機制深度解析

1. Stub/Skeleton 自動生成 (JDK 1.2+)

  • Stub:客戶端代理,實現遠程接口,負責參數序列化 (Marshalling)、發送 JRMP 請求、等待響應、反序列化 (Unmarshalling) 返回值/異常。
  • Skeleton:服務端分發器,接收請求、反序列化參數、調用實際實現、序列化結果返回。**JDK 1.2+ 內置於 Stub/反射機制中,無需單獨生成 skeleton 類**。
  • 生成工具:rmic -v1.2 TimeKeeperImpl (生成 TimeKeeperImpl_Stub.class)。**現代 JDK (5+) 運行時動態生成 Stub (動態代理),無需預編譯 rmic**,但部署到舊容器/特定環境仍需預生成。

2. 參數傳遞語義:序列化 vs 遠程引用

參數類型傳遞方式說明
基本類型 (int, long...)按值直接複製字節
實現 Serializable按值 (深度複製)對象圖完整序列化傳輸,**遠程端操作副本不影響源端**
實現 Remote按引用傳遞 Stub 引用,**遠程端調用方法實際路由回源端對象**
既 Serializable 又 Remote按引用 (Remote 優先)傳遞 Stub

3. 動態類加載 (RMI Class Loading)

  • 當 Stub 反序列化參數/返回值時,若本地 CLASSPATH 缺少對應類,**RMI 運行時會從代碼基 (Codebase) 下載類字節碼**。
  • Codebase 設置:服務端啟動參數 -Djava.rmi.server.codebase=https://host:port/classes/ (需 HTTP 服務器托管 JAR)。
  • 安全管理器:必須安裝 RMISecurityManager,否則拒絕下載類 (防止惡意代碼執行)。
  • 支持協議:HTTP, FTP, file (file 僅本地測試)。

4. 安全管理器與權限策略

// 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

常見問題與調試技巧

  1. Connection refused:註冊表未啟動 (執行 rmiregistry 或代碼 LocateRegistry.createRegistry)、端口被佔用、防火牆攔截。
  2. ClassNotFoundException (Stub):客戶端 CLASSPATH 缺少 Stub 類,或 Codebase 配置錯誤導致動態下載失敗。
  3. ClassCastException:Stub 版本不匹配 (服務端重新編譯未同步客戶端),或接口定義不一致。
  4. RemoteException: Marshal/Unmarshal error:對象未實現 Serializable、存在非瞬態非 Serializable 字段、循環引用、版本不匹配 (serialVersionUID)。
  5. 動態下載類失敗:Codebase URL 不可訪問 (需 HTTP 服務器)、安全策略禁止下載、JAR 包結構錯誤。

總結

RMI 通過 遠程接口 + UnicastRemoteObject + 註冊表 + JRMP + 序列化 構建了純 Java 的透明分布式對象模型。理解 Stub/Skeleton 代理模式、序列化語義、動態類加載、安全管理器機制,不僅能構建 RMI 應用,更是掌握 EJB、Jini、JMX、分布式 Spring (RMI 導出器) 等上層框架的內功心法。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved