“嗨!还有一个更开心的话题: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。感谢有趣的课程。”