Java RMI(Remote Method Invocation)是一种强大的技术,它允许对象调用运行在另一个 Java 虚拟机(JVM)中的对象的方法。RMI 为创建分布式应用程序提供了一个简单直接的模型,使开发人员能够构建和部署可轻松跨网络(包括互联网)运行的系统。它是 Java SE(标准版)平台的一部分。

尽管 Java RMI 有其优势,但随着网络服务和其他远程通信技术(如 REST API 和 gRPC)的兴起,Java RMI 的使用已经减少。不过,对于某些以 Java 为中心的分布式应用程序来说,RMI 仍然是一个强大的选择。

创建远程服务需要以下几个步骤:

制作远程接口

远程接口定义客户可以远程调用的方法,客户将用它作为服务的类类型。

import java.rmi.*;

public interface MyRemote extends Remote {

    String sayHello() throws RemoteException;
}

有以下几点注意:

  1. 声明所有的方法都抛出RemoteException
    每次远程方法调用都必须考虑成“有风险的”。因为这是通过网络和I/O来完成的,网络和I/O是有风险的,会失败!随时随地都可能抛出异常。在每个方法中声明RemoteException,强制客户注意并承认事情可能会出错。
  2. 确定参量和返回值是属于原语(primitive)类型或Serializable类型
    远程方法的任何参量都必须被打包并通过网络运送,这要靠序列化来完成。返回值也是一样。如果你使用原语类型、字符串,以及许多API中存在的类型(包括数组和集合),不会有问题。如果你传送自己的类型,就必须保证你的类实现了Serializable

实现远程接口

你的服务必须实现远程接口,带有客户要调用的方法的接口。

import java.rmi.*;
import java.rmi.server.*;

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    protected MyRemoteImpl() throws RemoteException {
    }

    private static final long serialVersionUID = 1L;

    @Override
    public String sayHello()  {
        return "Server says, 'Hey'";
    }

    public static void main(String[] args) {
        try {
            MyRemote service = new MyRemoteImpl();
            Naming.rebind("RemoteHello", service);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

为了成为远程服务对象,你的对象需要某些和“远程”有关的功能,最简单的方式是扩展UnicastRemoteObject(来自java.rmi.server包),让这个类(你的超类)做这些工作。

UnicastRemoteObject实现Serializable,所以我们需要serialVersionUID字段

编写一个声明RemoteException的无参量构造器,当类被实例化时,其超类的构造器总是会被调用。如果超类的构造器抛出一个异常,那么你没有选择,你的构造器也要抛出一个异常。

OK,现在有了一个远程服务,必须让它可以被远程客户调用。要做的是实例化它并放进RMI Regstry(RMI Registry 必须正在运行,否则注册会失败)。当注册这个实现对象时,RMI 系统其实注册的是桩(RMI 将客户辅助对象称为stub,也叫桩,服务辅助对象称为 skeleton,也叫骨架),因为这是客户真正需要的。注册服务是用java.rmi.Naming类的静态rebind()方法。

运行 rmiregistry

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class StartRmiRegistry {
    public static void main(String[] args) {
        try {
            // Start RMI registry on port 1099
            Registry registry = LocateRegistry.createRegistry(1099);
            System.out.println("RMI Registry started on port 1099");

            // Prevent the application from exiting immediately
            Object keepAlive = new Object();
            synchronized(keepAlive) {
                keepAlive.wait();
            }
        } catch (Exception e) {
            System.err.println("Failed to start RMI registry: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

启动服务

运行MyRemoteImpl,将对象在RMIRegistry中注册。

运行客户端

import java.rmi.*;

public class MyRemoteClient {
    public static void main(String[] args) {
        new MyRemoteClient().go();
    }

    public void go() {
        try {
            MyRemote service = (MyRemote) Naming.lookup("rmi://localhost/RemoteHello");

            String s = service.sayHello();

            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MyRemote service = (MyRemote) Naming.lookup("rmi://localhost/RemoteHello");

  • 客户总是使用远程接口作为服务类型,事实上,客户不需要知道远程服务的实际类名。
  • lookup()方法返回Object类型,必须把它转成接口。
  • RemoteHello,这必须是服务注册时用的名称。
  • rmi://localhost/,服务运行的主机名或者IP地址

标签: none

评论已关闭