“嗨!還有一個更開心的話題:RMI。RMI代表遠程方法調用。換句話說,RMI是一種機制,允許來自一台Java機器的對象調用來自另一台Java機器的對象的方法,即使它們在不同的計算機,在不同的國家,或在地球的不同角落。”

馬紹爾群島 - 1

“哇!聽起來棒極了。”

“是的。但我只會試著給你一個概述。有了這個,如果你挖得太深,你可能會對它如何工作的細微差別感到困惑。”

“但如果不走極端,那麼 RMI 不僅非常簡單,而且極大地簡化了程序員的生活。為此,我們向它致以最深切的敬意。”

“所以,我們希望 Java 程序中的一個對象調用另一個 Java 程序中的對象的方法。不管這些程序在哪裡運行。”

“讓我們考慮一個最簡單的例子:當兩個程序在同一台計算機上運行時。 要讓程序通過互聯網進行交互,您需要配置 JVM 的權限,但我們今天不做介紹。”

“在Java中,你只能遠程調用接口的方法,不能調用類。”

“那麼,我們有兩個程序,它們如何互相調用對方的方法呢?”

“讓我們考慮這樣一種情況,其中一個程序包含一個對象,而第二個程序想要調用該對象的方法。我們將第一個程序稱為服務器,將第二個程序稱為客戶端。

“首先,我將提供一些示例代碼,然後我們將對其進行分析。”

“那麼我們的程序會做什麼呢?”

“嗯。好吧,為了簡單起見,程序將有一個方法來反轉傳遞給它的字符串。”

“很簡單。”

“好,那我們開始吧:”

“首先,我們需要一個能滿足我們要求的接口:”

程序間通信的接口
interface Reverse extends Remote
{
 public String reverse(String str) throws RemoteException;
}

“我創建了一個 Reverse 接口並向其添加了一個 Remote 標記接口,以及一個 RemoteException。調用該方法時可能會發生意外錯誤。如果有任何錯誤,則會拋出此異常。”

“現在我們需要編寫一個實現這個接口的服務器類:”

服務器類
class ReverseImpl implements Reverse
{
 public String reverse(String str) throws RemoteException
 {
  return new StringBuffer(str).reverse().toString();
 }
}

“我明白了。我們用這個方法反轉字符串。”

“是的。”

“現在我們需要讓這個對象可以從另一個程序調用。這是你如何做到的:”

對象共享
public static final String UNIC_BINDING_NAME = "server.reverse";

public static void main(String[] args) throws Exception
{
 // Create an object to be accessible remotely.
 final ReverseImpl service = new ReverseImpl();

 // Create a registry of shared objects.
 final Registry registry = LocateRegistry.createRegistry(2099);
 // Create a stub for receiving remote calls.
 Remote stub = UnicastRemoteObject.exportObject(service, 0);
 // Register the stub in the registry.
 registry.bind(UNIC_BINDING_NAME, stub);

 // Put the main thread to sleep, or else the program will exit.
 Thread.sleep(Integer.MAX_VALUE);
}

“我會逐行解釋的。”

"在第 1 行中,我們在UNIC_BINDING_NAME變量 中為我們的遠程對象(可遠程訪問的對象)存儲一個唯一名稱(我們編寫的) 。如果程序使多個對象可訪問,則每個對像都必須有自己的唯一名稱。我們的對象的唯一名稱是‘server.reverse’。”

在第 6 行,我們創建了一個可遠程訪問的ReverseImpl對象。將調用其方法。”

"在第 9 行,我們創建了一個稱為註冊表的特殊對象。我們需要使用它來註冊我們共享的對象。JVM 稍後將與它們交互。2099 是一個端口(另一個程序可以用來訪問我們的唯一編號)對象註冊表)。”

“換句話說,要訪問一個對象,你需要知道對象註冊表的唯一編號(端口)和對象的唯一名稱,並且與遠程對象實現的接口具有相同的接口。”

“我明白了。比如:打電話(需要一個號碼)並詢問比爾(一個物體的名稱)?”

“嗯。現在,我們繼續吧。”

"在第 11 行 ,我們創建了一個存根。存根是一個特殊的對象,它接收有關遠程調用的信息,將其解包,反序列化方法參數,並調用所需的方法。然後它序列化結果或異常,如果有的話,並將其全部發回給調用者。”

“我明白了。幾乎。你說它‘反序列化方法參數’。那麼,這意味著遠程方法的參數必須是可序列化的?”

“是的。否則你會如何通過網絡發送它們?是的,有例外,即通過引用傳遞的對象,但我們今天不會談論它們。”

“我們會這樣說:你不能傳遞不可序列化的對象,但如果你真的想傳遞,那麼你可以。但這很痛苦,你知道的。”

“好的。”

“那我們繼續吧。”

在第 13 行,我們在註冊表中以唯一名稱註冊對象的存根。”

在第 16 行,我們讓主線程休眠。所有的遠程調用都在單獨的線程上處理。重要的是程序正在運行。所以我們在這裡簡單地讓主線程休眠。就是這樣。”

“好的。”

“太好了,那麼這是一個客戶端的例子:”

使用遠程對象
public static final String UNIC_BINDING_NAME = "server.reverse";

public static void main(String[] args) throws Exception
{
 // Create a registry of shared objects
 final Registry registry = LocateRegistry.createRegistry(2099);

 // Get the object (actually, this is a proxy object)
 Reverse service = (Reverse) registry.lookup(UNIC_BINDING_NAME);

 // Call the remote method
 String result = service.reverse("Home sweet home.");
}

“我將逐行解釋這段代碼:”

第 1 行 是遠程對象的唯一名稱。這在客戶端和服務器上必須相同。”

在第 6 行 ,我們創建了一個 «遠程對象註冊表»。它的端口 (2099) 必須與服務器應用程序的註冊表端口相同。”

"在第 9 行,我們從註冊表中獲取對象。返回的對像是代理對象,並轉換為接口。該接口必須繼承 Remote 標記接口。"

在第 12 行,我們調用接口的方法,就好像對像是在同一個程序中創建的一樣。沒有區別。”

“太棒了!現在我可以編寫分佈式應用程序了。或者像 Battleship for Android 這樣的遊戲。”

“別這樣,阿米戈!Android 操作系統在第三次嘗試接管世界後於 27 世紀被禁止。機器人無法訪問它。沒有任何方法可以讓你遠離它. 你會開始跑來跑去大喊,«殺死所有人類!»”

“嗯。好吧。但我還是得問迭戈。你永遠不知道,也許他會有一些有趣的事情要說。”

“那你去問問他吧,好吧,明天再說吧。”

“再見,Rishi。感謝有趣的課程。”