Java RMI(Remote Method Invocation)是一種允許Java虛擬機之間進行通信和交互的技術。它使得遠程Java對象能夠像本地對象一樣被訪問和操作,從而簡化了分散式應用程式的開發。一些應用依然會使用 RMI 來實現通信和交互,今天的內容我們來聊聊 RMI 的那些事兒。 一、先 ...
Java RMI(Remote Method Invocation)是一種允許Java虛擬機之間進行通信和交互的技術。它使得遠程Java對象能夠像本地對象一樣被訪問和操作,從而簡化了分散式應用程式的開發。一些應用依然會使用 RMI 來實現通信和交互,今天的內容我們來聊聊 RMI 的那些事兒。
一、先來瞭解一下概念
RMI原理
RMI的基本思想是遠程方法調用。客戶端調用遠程方法時,實際上是發送一個調用請求到伺服器,由伺服器執行該方法,並將結果返回給客戶端。RMI通過存根(Stub)和骨架(Skeleton)類來實現遠程調用,存根位於客戶端,而骨架位於伺服器端。
RMI組件
- 遠程介面:必須繼承自
java.rmi.Remote
介面,並聲明拋出RemoteException
。 - 遠程對象:實現了遠程介面的類。
- RMI伺服器:提供遠程對象,並處理客戶端的調用請求。
- RMI客戶端:發起遠程方法調用請求。
- 註冊服務(Registry):提供服務註冊與獲取,類似於目錄服務。
數據傳遞
RMI使用Java序列化機制來傳遞數據。客戶端將方法參數序列化後通過網路發送給伺服器,伺服器反序列化參數並執行遠程方法,然後將結果序列化回傳給客戶端。
RMI案例
以下是一個簡單的RMI案例,包括伺服器和客戶端的實現思路,下文V 將再用代碼來解釋:
伺服器端
- 實現一個遠程介面,例如
PersonController
,包含一個遠程方法queryName
。 - 創建該介面的具體實現類
PersonControllerImpl
,併在其中實現遠程方法。 - 在伺服器的
main
方法中,實例化遠程對象,創建RMI註冊表,並使用Naming.rebind
將遠程對象綁定到指定名稱。
客戶端
- 通過
Naming.lookup
方法,使用RMI註冊表提供的名稱獲取遠程對象的存根。 - 調用存根上的方法,就像調用本地方法一樣,實際上是在調用伺服器上的遠程方法。
RMI的局限性
- 語言限制:RMI是Java特有的技術,不能直接用於非Java應用程式。
- 安全性問題:RMI的序列化機制可能帶來安全風險,不建議將1099埠暴露在公網上。
- 性能和擴展性:RMI的性能受網路延遲和帶寬影響,且在高併發情況下可能面臨擴展性限制。
RMI的應用場景
RMI適用於需要Java程式之間進行遠程通信的場景,如分散式銀行系統、游戲伺服器、股票交易系統和網上商城等。接下來一起看一個簡單的案例使用吧。
二、案例使用
先來搞一個簡單的Java RMI伺服器端和客戶端的實現案例。這個案例中,伺服器端將提供一個名為HelloWorld
的遠程服務,客戶端將調用這個服務並列印返回的問候語。
伺服器端實現
- 定義遠程介面:
伺服器和客戶端都需要這個介面。它必須繼承自java.rmi.Remote
介面,並且所有遠程方法都要聲明拋出RemoteException
。
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloWorld extends Remote {
String sayHello() throws RemoteException;
}
- 實現遠程介面:
創建一個實現了上述介面的類,並實現遠程方法。
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
public class HelloWorldImpl extends UnicastRemoteObject implements HelloWorld {
protected HelloWorldImpl() throws RemoteException {
super();
}
@Override
public String sayHello() throws RemoteException {
return "Hello, World!";
}
}
- 設置RMI伺服器:
創建一個主類來設置RMI伺服器,綁定遠程對象到RMI註冊表。
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class HelloWorldServer {
public static void main(String[] args) {
try {
// 創建遠程對象
HelloWorld helloWorld = new HelloWorldImpl();
// 獲取RMI註冊表的引用,併在指定埠上創建或獲取註冊表實例
LocateRegistry.createRegistry(1099);
// 將遠程對象綁定到RMI註冊表中,客戶端可以通過這個名字訪問遠程對象
Naming.bind("rmi://localhost/HelloWorld", helloWorld);
System.out.println("HelloWorld RMI object bound");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}
}
客戶端實現
- 調用遠程服務:
客戶端使用RMI註冊表的名字來查找遠程對象,並調用其方法。
import java.rmi.Naming;
import java.rmi.RemoteException;
public class HelloWorldClient {
public static void main(String[] args) {
try {
// 使用RMI註冊表的名字查找遠程對象
HelloWorld helloWorld = (HelloWorld) Naming.lookup("rmi://localhost/HelloWorld");
// 調用遠程方法
String response = helloWorld.sayHello();
System.out.println("Response: " + response);
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
來詳細解釋吧
- 遠程介面 (
HelloWorld
): 這是伺服器和客戶端之間通信的協議。它定義了可以被遠程調用的方法。 - 遠程對象實現 (
HelloWorldImpl
): 這是遠程介面的一個實現。RMI調用實際上會調用這個實現中的方法。 - 伺服器 (
HelloWorldServer
): 負責創建遠程對象的實例,並將這個實例綁定到RMI註冊表中。這樣客戶端就可以通過註冊表的名字來訪問這個對象。 - 客戶端 (
HelloWorldClient
): 使用RMI註冊表的名字來查找伺服器上的遠程對象,並調用其方法。
接下來就可以編譯所有類文件,運行伺服器端程式,確保RMI註冊表已經啟動(在某些Java版本中會自動啟動),再運行客戶端程式,搞定。註意一下哈,由於RMI使用Java序列化機制,因此客戶端和伺服器的類路徑必須一致或相容。
三、RMI 在分散式銀行系統中的應用
接下來V哥要介紹業務場景下的應用了,拿在分散式銀行系統中來說,我們可以使用RMI來實現不同銀行分行之間的通信,例如,實現賬戶信息的查詢、轉賬等操作。以下是一個簡化的示例,其中包括兩個基本操作:查詢賬戶餘額和執行轉賬,按步驟一步一步來吧。
步驟1: 定義遠程介面
首先,定義一個遠程介面BankService
,它將被各個分行實現以提供銀行服務。
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface BankService extends Remote {
double getAccountBalance(String accountNumber) throws RemoteException;
boolean transferFunds(String fromAccount, String toAccount, double amount) throws RemoteException;
}
步驟2: 實現遠程介面
接下來,實現這個介面來創建遠程對象,這個對象將提供實際的銀行服務。
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
public class BankServiceImpl extends UnicastRemoteObject implements BankService {
private Map<String, Double> accounts = new HashMap<>();
protected BankServiceImpl() throws RemoteException {
super();
// 初始化一些賬戶信息
accounts.put("123456789", 5000.00);
accounts.put("987654321", 1000.00);
}
@Override
public double getAccountBalance(String accountNumber) throws RemoteException {
return accounts.getOrDefault(accountNumber, 0.00);
}
@Override
public boolean transferFunds(String fromAccount, String toAccount, double amount) throws RemoteException {
if (accounts.containsKey(fromAccount) && accounts.get(fromAccount) >= amount) {
accounts.put(fromAccount, accounts.get(fromAccount) - amount);
accounts.merge(toAccount, amount, Double::sum);
return true;
}
return false;
}
}
步驟3: 設置RMI伺服器
伺服器端將創建BankService
的遠程對象實例,並將其綁定到RMI註冊表中。
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class BankServer {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099); // 創建RMI註冊表
BankService bankService = new BankServiceImpl();
Naming.rebind("//localhost/BankService", bankService); // 綁定遠程對象
System.out.println("BankService is ready for use.");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}
}
步驟4: 實現RMI客戶端
客戶端將使用RMI註冊表的名字來查找遠程對象,並調用其方法。
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class BankClient {
public static void main(String[] args) {
try {
BankService bankService = (BankService) Naming.lookup("//localhost/BankService");
System.out.println("Account balance: " + bankService.getAccountBalance("123456789"));
// 執行轉賬操作
boolean isTransferSuccess = bankService.transferFunds("123456789", "987654321", 200.00);
if (isTransferSuccess) {
System.out.println("Transfer successful.");
} else {
System.out.println("Transfer failed.");
}
// 再次查詢餘額
System.out.println("New account balance: " + bankService.getAccountBalance("123456789"));
} catch (RemoteException | NotBoundException e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
來詳細解釋一下
- 遠程介面 (
BankService
): 定義了兩個方法:getAccountBalance
用於查詢賬戶餘額,transferFunds
用於執行轉賬操作。 - 遠程對象實現 (
BankServiceImpl
): 實現了BankService
介面。它使用一個HashMap
來模擬賬戶和餘額信息。 - 伺服器 (
BankServer
): 設置了RMI伺服器,將BankService
的實現綁定到RMI註冊表中,供客戶端訪問。 - 客戶端 (
BankClient
): 查找RMI註冊表中的BankService
服務,並調用其方法來查詢餘額和執行轉賬。
擼完代碼後,編譯所有類文件,運行伺服器端程式BankServer
,再運行客戶端程式BankClient
,測試效果吧。
最後
最後V哥要提醒一下,在實際的銀行系統中,當然還需要考慮安全性、事務性、持久性以及錯誤處理等多方面的因素,RMI的網路通信也需要在安全的網路環境下進行,以防止數據泄露或被篡改。你在應用中是怎麼使用 RMI 的,歡迎關註威哥愛編程,一起交流一下哈。