RMI远程调用

黑派客     最近更新时间:2020-01-17 09:39:58

584

Java的RMI远程调用是指,一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法。RMI是Remote Method Invocation的缩写。

提供服务的一方我们称之为服务器,而实现远程调用的一方我们称之为客户端。

我们先来实现一个最简单的RMI:服务器会提供一个WorldClock服务,允许客户端获取指定时区的时间,即允许客户端调用下面的方法:

LocalDateTime getLocalDateTime(String zoneId);

要实现RMI,服务器和客户端必须共享同一个接口。我们定义一个WorldClock接口,代码如下:

public interface WorldClock extends Remote {
    LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
}

Java的RMI规定此接口必须派生自java.rmi.Remote,并在每个方法声明抛出RemoteException

下一步是编写服务器的实现类,因为客户端请求的调用方法getLocalDateTime()最终会通过这个实现类返回结果。实现类WorldClockService代码如下:

public class WorldClockService implements WorldClock {
    @Override
    public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {        return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
    }
}

现在,服务器端的服务相关代码就编写完毕。我们需要通过Java RMI提供的一系列底层支持接口,把上面编写的服务以RMI的形式暴露在网络上,客户端才能调用:

public class Server {
    public static void main(String[] args) throws RemoteException {
        System.out.println("create World clock remote service...");        // 实例化一个WorldClock:
        WorldClock worldClock = new WorldClockService();        // 将此服务转换为远程服务接口:
        WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);        // 将RMI服务注册到1099端口:
        Registry registry = LocateRegistry.createRegistry(1099);        // 注册此服务,服务名为"WorldClock":
        registry.rebind("WorldClock", skeleton);
    }
}

上述代码主要目的是通过RMI提供的相关类,将我们自己的WorldClock实例注册到RMI服务上。RMI的默认端口是1099,最后一步注册服务时通过rebind()指定服务名称为"WorldClock"

下一步我们就可以编写客户端代码。RMI要求服务器和客户端共享同一个接口,因此我们要把WorldClock.java这个接口文件复制到客户端,然后在客户端实现RMI调用:

public class Client {
    public static void main(String[] args) throws RemoteException, NotBoundException {        // 连接到服务器localhost,端口1099:
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);        // 查找名称为"WorldClock"的服务并强制转型为WorldClock接口:
        WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");        // 正常调用接口方法:
        LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");        // 打印调用结果:
        System.out.println(now);
    }
}

先运行服务器,再运行客户端。从运行结果可知,因为客户端只有接口,并没有实现类,因此,客户端获得的接口方法返回值实际上是通过网络从服务器端获取的。整个过程实际上非常简单,对客户端来说,客户端持有的WorldClock接口实际上对应了一个“实现类”,它是由Registry内部动态生成的,并负责把方法调用通过网络传递到服务器端。而服务器端接收网络调用的服务并不是我们自己编写的WorldClockService,而是Registry自动生成的代码。我们把客户端的“实现类”称为stub,而服务器端的网络服务类称为skeleton,它会真正调用服务器端的WorldClockService,获取结果,然后把结果通过网络传递给客户端。整个过程由RMI底层负责实现序列化和反序列化:

展开阅读全文