Java安全
Java 基础知识
01概述
02变量
03运算符
04程序控制语句
05数组
06面向对象编程
07代码练习
08重载
09作用域
10构造方法&构造器
11this
12包
13修饰符
14封装
15继承
16super
17覆盖&重写
18多态
19零钱通项目
20类变量&类方法
21抽象类
22接口
23内部类
24枚举
25泛型
26常用API
27lambda表达式
28正则表达式
29异常
30File&IO流
31日志技术
32多线程
33网络编程
01反射
02反序列化
03JVM
04JDBC
05RMI
06JRMP
07JNDI
08CDI
09JPA
10Servlet
11Filter
12MVC模型
13MVC框架
14类的加载机制
15Maven
16注解
17ORM
18CC链
19JNDI注入
Log4j2
-
+
首页
05RMI
## 概述 RMI(Remote Method Invocation,远程方法调用)是 Java 提供的一套分布式通信框架,允许一个 JVM(Java 虚拟机)中的程序像调用本地方法一样,调用另一个 JVM 中对象的方法。它是 Java 分布式应用的核心技术之一,底层依赖 JRMP(Java Remote Method Protocol)协议(默认),也可适配其他协议(如 IIOP)。 ## 核心组件 | 组件 / 概念 | 描述 | | ------------------------- | ------------------------------------------------------------ | | Remote 接口 | 所有远程对象的接口必须直接或间接继承java.rmi.Remote(标记接口),表明该接口中的方法可被远程调用。 | | RemoteException | 远程方法必须声明抛出java.rmi.RemoteException(或其子类),用于处理远程调用中可能出现的网络异常、序列化失败等问题。 | | 远程对象(Remote Object) | 实现Remote接口的类的实例,部署在服务端,提供可被远程调用的方法。 | | RMI Registry | 服务端的 “注册中心”(默认端口 1099),用于存储远程对象的 “名称” 与 “引用”(类似 JNDI 的命名服务),客户端通过名称查找远程对象。 | | Stub(存根) | 客户端的代理对象,由 RMI 自动生成。客户端调用远程方法时,实际调用的是 Stub 的方法,Stub 负责将调用信息(方法名、参数)序列化后通过网络发送给服务端。 | | Skeleton(骨架) | 服务端的代理对象,由 RMI 自动生成(JDK 5 + 后被动态代理替代,概念保留)。负责接收 Stub 发送的请求,反序列化数据,调用实际远程对象的方法,并将结果序列化后返回给 Stub。 | | JRMP 协议 | RMI 默认的通信协议,基于 Java 序列化机制传输数据(参数、返回值、对象引用等),是 RMI 与其他远程调用框架(如 gRPC)的核心区别。 | ## 工作流程 在JVM之间通信时,RMI对远程对象和非远程对象的处理方式是不一样的,它并没有直接把远程对象复制一份传递给客户端,而是传递了一个远程对象的Stub,Stub基本上相当于是远程对象的引用或者代理。 Stub对开发者是透明的,客户端可以像调用本地方法一样直接通过它来调用远程方法。Stub中包含了远程对象的定位信息,如Socket端口、服务端主机地址等等,并实现了远程调用过程中具体的底层网络通信细节,所以RMI远程调用逻辑是这样的:  ### 服务端 1. 创建远程对象实现。 2. 将远程对象**绑定**(注册)到 RMI 注册表,并关联一个名称。 ```java // 步骤1:定义远程接口(必须继承Remote,方法声明抛出RemoteException) import java.rmi.Remote; import java.rmi.RemoteException; public interface Calculator extends Remote { int add(int a, int b) throws RemoteException; // 远程方法 } // 步骤2:实现远程接口(需继承UnicastRemoteObject,自动生成Stub/Skeleton) import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class CalculatorImpl extends UnicastRemoteObject implements Calculator { // 必须显式声明构造器,并抛出RemoteException protected CalculatorImpl() throws RemoteException { super(); } @Override public int add(int a, int b) throws RemoteException { return a + b; // 核心业务逻辑 } } // 步骤3:启动RMI Registry并注册远程对象 import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Server { public static void main(String[] args) throws Exception { // 1. 创建远程对象实例 Calculator calculator = new CalculatorImpl(); // 2. 启动RMI Registry(默认端口1099,若已启动可省略) LocateRegistry.createRegistry(1099); // 3. 将远程对象注册到Registry,绑定名称为"calculatorService" Registry registry = LocateRegistry.getRegistry(); registry.bind("calculatorService", calculator); System.out.println("服务端已启动,远程对象已注册:rmi://localhost:1099/calculatorService"); } } ``` ### 客户端 1. 通过 RMI 注册表的 **查找** 功能,根据名称获取远程对象的 **Stub**。 2. 在本地调用 Stub 的方法。 ```java import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { public static void main(String[] args) throws Exception { // 1. 连接服务端的RMI Registry Registry registry = LocateRegistry.getRegistry("localhost", 1099); // 2. 通过名称查找远程对象(返回的是Stub代理) Calculator calculator = (Calculator) registry.lookup("calculatorService"); // 3. 调用远程方法(像调用本地方法一样) int result = calculator.add(10, 20); System.out.println("远程调用结果:10 + 20 = " + result); // 输出:30 } } ``` ### 全流程解析 **服务端发布**: - 远程对象实例化后,通过UnicastRemoteObject自动导出(绑定到某个端口,准备接收请求); - RMI Registry 启动后,远程对象以 “名称”(如calculatorService)注册到 Registry,供客户端查找。 **客户端调用**: - 客户端通过 Registry 的lookup()方法获取远程对象的 Stub(代理); - 调用 Stub 的add()方法时,Stub 将参数(10, 20)序列化,通过 JRMP 协议发送到服务端; - 服务端的 Skeleton(或动态代理)接收请求,反序列化参数,调用实际CalculatorImpl的add()方法,将结果(30)序列化后返回; - 客户端 Stub 反序列化结果,返回给调用者。 使用RMI Registry之后,RMI的调用关系是这样的:  ## 动态加载类 RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,动态加载的对象class文件可以使用Web服务的方式进行托管。 这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。对于客户端而言,服务端返回值也可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,则同样需要有运行时动态加载额外类的能力。 客户端使用了与RMI注册表相同的机制。RMI服务端将URL传递给客户端,客户端通过HTTP请求下载这些类。  ## 与其他技术的关联 **与 JRMP**:JRMP 是 RMI 的默认传输协议,专门为 Java 对象传输设计,依赖 Java 序列化(这也是 RMI 安全风险的核心来源)。 **与 JNDI**:RMI Registry 可作为 JNDI 的命名服务,客户端可通过 JNDI 的InitialContext.lookup()替代Registry.lookup()查找远程对象(如jndi:rmi://localhost:1099/calculatorService)。 **与序列化**:RMI 的参数、返回值、远程对象引用均通过 Java 序列化传输,这使得 RMI 成为反序列化漏洞的高发区(攻击者可构造恶意序列化数据,通过 RMI 传输触发漏洞)。
毛林
2025年10月28日 10:37
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码