Java RMI(Remote Method Invocation,远程方法调用),Java RMI是Java编程语言中用于实现远程过程调用的应用程序编程接口(API)。它允许一个Java虚拟机(JVM)上的对象调用另一个JVM上对象的方法,就像调用本地方法一样。这种机制使得开发人员能够在网络环境中分布处理任务,提高应用程序的可扩展性和可靠性。
RMI 的基本思想是远程方法调用,即客户端调用某个方法,其本质是将这个方法的调用请求发送给服务器,由服务器代为执行,并将执行结果回送客户端。对于客户端而言,调用 RMI 方法就像调用本地方法一样,不需要任何配置;对于服务器而言,RMI相当于要处理一个来自客户端的“请求”,这个请求针对某个方法。
提供服务注册与服务获取。Server 端向 Registry 注册服务(如地址、端口等信息),Client 端从 Registry 获取远程对象的一些信息(如地址、端口等),然后进行远程调用。
提供具体服务,并注册到 Registry 中。Server 的功能:
(1)建立RMI服务器;
(2)侦听客户端连接请求;
(3)连接RMI客户端;
(4)接受客户端发送过来的要执行的方法名称、实参等信息;
(5)找到需要代理执行的方法,并使用反射机制执行该方法,将方法执行的结果回传给客户端,断开与客户端的连接。
用于调用远程方法,并接收返回结果。Client 的功能:
(1)连接RMI服务器;
(2)作为远程方法的消费者,从Registry获取远程方法的相关信息并且调用;
(3)等待服务器返回这个方法在服务器端执行的结果。
(1)启动 RMI Registry 服务,可以指定服务监听的端口,也可以使用默认的端口(1099)。
(2)Server 端在本地先实例化一个提供服务的实现类,然后通过 RMI 提供的 Naming/Context/Registry 等类的 bind 或 rebind 方法将实例化好的实现类注册到 RMI Registry 上,并对外暴露一个名称。
(3)Client 端通过本地的接口和一个已知的名称(即 RMI Registry 暴露出的名称),使用 RMI 提供的 Naming/Context/Registry 等类的lookup 方法从 RMI Service 那拿到实现类。
(1)定义远程接口:远程接口必须继承 java.rmi.Remote 接口标记,并且所有方法必须抛出 java.rmi.RemoteException 异常。
(2)实现远程接口:远程接口的具体实现类必须扩展 java.rmi.server.UnicastRemoteObject 类,并覆盖 Remote 接口中定义的所有方法。
(3)创建桩(Stub)和框架(Skeleton):桩是客户端的代理对象,它扩展了远程接口并实现了与远程对象通信的必要协议;框架是服务端的代理对象,它将远程接口的具体实现绑定到网络层。这些通常由RMI工具自动生成。
(4)启动 RMI 服务器并注册远程服务。
(5)编写客户端代码以查找和调用远程服务。
(1)定一个实体,实现 Serializable 接口,标记可以进行序列化。代码如下:
package com.hxstrive.rmi; import java.io.Serializable; /** * 用户实体 * @author hxstrive.com */ public class User implements Serializable { private static final long serialVersionUID = 6490921832856589236L; private String name; private Integer age; private String skill; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSkill() { return skill; } public void setSkill(String skill) { this.skill = skill; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", skill='" + skill + '\'' + '}'; } }
(2)定义一个 RMI 接口,继承 Remote 接口。Remote 接口是 Java RMI 框架中的一个标记接口,用于标识一个接口定义的方法可以从非本地的JVM上调用。任何希望被远程调用的接口都必须扩展 java.rmi.Remote 接口,并且其方法必须声明抛出 java.rmi.RemoteException。例如:
package com.hxstrive.rmi; import java.rmi.Remote; import java.rmi.RemoteException; /** * RMI 接口 * @author hxstrive.com */ public interface UserService extends Remote { /** * 查找用户 * * @param userId 用户ID * @return * @throws RemoteException */ User findUser(String userId) throws RemoteException; }
(3)实现 RMI 接口,必须继承 java.rmi.server.UnicastRemoteObject 类。java.rmi.server.UnicastRemoteObject 是 Java RMI(Remote Method Invocation,远程方法调用)框架中的一个类,它提供了将远程对象导出到 RMI 运行时以便客户端可以调用的功能。当你有一个实现了远程接口的类,并且你希望这个类的对象能够被远程访问时,你可以通过让这个类继承 UnicastRemoteObject 来实现。例如:
package com.hxstrive.rmi; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; /** * 服务具体实现 * @author hxstrive.com */ public class UserServiceImpl extends UnicastRemoteObject implements UserService { protected UserServiceImpl() throws RemoteException { } @Override public User findUser(String userId) throws RemoteException { // 模拟根据用户 ID 查询用户信息 if ("00001".equals(userId)) { User user = new User(); user.setName("金庸"); user.setAge(100); user.setSkill("写作"); return user; } throw new RemoteException("查无此人"); } }
(4)创建 RMI 服务端,将 RMI 接口和实现类绑定到 RMI Registry,并启动服务。代码如下:
package com.hxstrive.rmi; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; /** * 服务端 * @author hxstrive.com */ public class HelloServer { public static void main(String[] args) { try { UserService userService = new UserServiceImpl(); // 创建并导出接受指定 port 请求的本地主机上的 Registry 实例。 LocateRegistry.createRegistry(1900); // 注册远程对象 Naming.bind("//localhost:1900/UserService", userService); System.out.println("HelloServer 启动成功"); } catch (Exception e) { e.printStackTrace(); } } }
运行代码,启动服务端,输出如下:
HelloServer 启动成功
(5)创建客户端,通过 RMI Registry 查找远程对象,并调用方法。代码如下:
package com.hxstrive.rmi; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; /** * 客户端 * @author hxstrive.com */ public class HelloClient { public static void main(String[] args) { try { // 查找远程对象 UserService userService = (UserService) Naming.lookup("//localhost:1900/UserService"); // 调用远程方法 User user = userService.findUser("00001"); System.out.println(user); } catch (MalformedURLException e) { System.out.println("url 格式异常"); } catch (RemoteException e) { System.out.println("创建对象异常"); e.printStackTrace(); } catch (NotBoundException e) { System.out.println("对象未绑定"); } } }
运行代码,启动客户端,输出如下:
User{name='金庸', age=100, skill='写作'}