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.注册开启远程服务
注册服务共有三种方式:
-
LocateRegistry 类的对象的 rebind() 和 lookup() 来实现绑定注册和查找远程对象的
-
利用命名服务 java.rmi.Naming 类的 rebind() 和 lookup() 来实现绑定注册和查找远程对象的
-
利用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