首页 > 代码库 > RMI 使用笔记

RMI 使用笔记

Java 远程方法调用,即 Java RMI( Java Remote Method Invocation ) 。顾名思义,可以使客户机上运行的程序能够调用远程服务器上的对象(方法)。

下面主要介绍一下使用步骤:

1.定义远程接口(服务端)

远程接口定义出可以让客户远程调用的方法。

此接口必须实现 java.rmi.Remote 接口,来表示其支持远程调用;同时其中声明的所有方法,需要抛出RemoteException异常,因为远程调用的不稳定性(如网络原因等),这样可以让客户端在调用失败时进行相应的处理。

public interface DemoService extends Remote {
    String sayHello() throws RemoteException;
}

 

2.实现远程接口(服务端)

远程接口的实现类如果想要被远程访问,可以有如下实现方式:

继承java.rmi.server.UnicastRemoteObject

public class DemoServerImpl extends UnicastRemoteObject implements DemoService{
?
    public DemoServerImpl() throws RemoteException {
        // 因为 UnicastRemoteObject 构造器抛出 RemoteException
        // 所以此处只能声明一个构造器并抛出对应异常
    }
?
    @Override
    public String sayHello() throws RemoteException {
        return "Hello World";
    }
}

如果不想继承UnicastRemoteObject类,则需要使用 UnicastRemoteObject类的静态方法exportObject(Remote obj, int port)将对象导出

其中如果端口设为 0 的话,则表示任何合适的端口都可用来监听客户连接

public class DemoServerImpl implements DemoService{
?
    public DemoServerImpl() throws RemoteException {
        UnicastRemoteObject.exportObject(this, 0);
    }
?
    @Override
    public String sayHello() throws RemoteException {
        return "Hello World";
    }
}

这两者方法本质上是一样的,在UnicaseRemoteObject类的构造方法中,其实也是调用了exportObject方法

// UnicaseRemoteObject中的部分源码
protected UnicastRemoteObject() throws RemoteException
{
    this(0);
}
// if port is zero, an anonymous port is chosen
protected UnicastRemoteObject(int port) throws RemoteException
{
    this.port = port;
    exportObject((Remote) this, port);
}

 

3.启动 RMI 注册表

注册表就像一个电话簿,启动后即可将提供的服务注册到其中,客户可以通过它查询到服务来进行调用

启动注册表有两种方法,一种是通过命令行rmiregistry来启动,另一种方式是通过LocateRegistry.createRegistry(int port)方法。

4.注册开启远程服务

注册服务共有三种方式:

  1. LocateRegistry 类的对象的 rebind() 和 lookup() 来实现绑定注册和查找远程对象的

  2. 利用命名服务 java.rmi.Naming 类的 rebind() 和 lookup() 来实现绑定注册和查找远程对象的

  3. 利用JNDI(Java Naming and Directory Interface,Java命名和目录接口) java.naming.InitialContext 类来 rebind() 和 lookup() 来实现绑定注册和查找远程对象的

其中第二种方式实际是对第一种方式的简单封装,在内部仍是调用Registry类的bind方法

// Naming 类的部分源码 (为了节省篇幅,去除了抛出异常部分)
public static void bind(String name, Remote obj) throws ...
{
    ParsedNamingURL parsed = parseURL(name);
    Registry registry = getRegistry(parsed);
?
    if (obj == null)
        throw new NullPointerException("cannot bind to null");
?
    registry.bind(parsed.name, obj);
}

 

服务测试类:

public class ServerTest {
    public static void main(String[] args) throws Exception{
        String name = "rmi.service.DemoService";
        // 创建服务
        DemoService service = new DemoServerImpl();
        // 创建本机 1099 端口上的 RMI 注册表
        Registry registry1 = LocateRegistry.createRegistry(1099);
      
        /***************** 以下为注册方法一 ************/
        // 将服务绑定到注册表中
        registry1.bind(name, service);
      
        /***************** 以下为注册方法二 ************/
        // Naming.bind(name, service);
      
        /***************** 以下为注册方法三 ************/
        //Context namingContext = new InitialContext();
        //namingContext.bind("rmi:" + name, service); // 此方式 name 需要以 rmi: 开头
      
    }
}

 

客户端测试类:

public class ClientTest {
    public static void main(String[] args) throws Exception {
        String name = "rmi.service.DemoService";
        /***************** 以下为查找服务方法一 ************/
        // 获取注册表
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // 查找对应的服务
        DemoService service = (DemoService) registry.lookup(name);
      
        /***************** 以下为查找服务方法二 ************/
        // DemoService service = (DemoService) Naming.lookup(name);
      
        /***************** 以下为查找服务方法三 ************/
        //Context namingContext = new InitialContext();
        //DemoService service = (DemoService) namingContext.lookup("rmi:" + name);
      
        // 调用服务
        System.out.println(service.sayHello());
    }
}

 

参考文章:https://segmentfault.com/a/1190000004494341

 

 

RMI 使用笔记