Java RMI技术详解与案例分析
Java RMI(Remote Method Invocation)是一种允许Java虚拟机之间进行通信和交互的技术。它使得远程Java对象能够像本地对象一样被访问和操作,从而简化了分布式应用程序的开发。一些应用依然会使用 RMI 来实现通信和交互,今天的内容我们来聊聊 RMI 的那些事儿。
一、先来了解一下概念
RMI原理
RMI的基本思想是远程方法调用。客户端调用远程方法时,实际上是发送一个调用请求到服务器,由服务器执行该方法,并将结果返回给客户端。RMI通过存根(Stub)和骨架(Skeleton)类来实现远程调用,存根位于客户端,而骨架位于服务器端。
RMI组件
- 远程接口:必须继承自
java.rmi.Remote
接口,并声明抛出RemoteException
。 - 远程对象:实现了远程接口的类。
- RMI服务器:提供远程对象,并处理客户端的调用请求。
- RMI客户端:发起远程方法调用请求。
- 注册服务(Registry):提供服务注册与获取,类似于目录服务。
数据传递
RMI使用Java序列化机制来传递数据。客户端将方法参数序列化后通过网络发送给服务器,服务器反序列化参数并执行远程方法,然后将结果序列化回传给客户端。
RMI案例
以下是一个简单的RMI案例,包括服务器和客户端的实现思路,下文V 将再用代码来解释:
服务器端
- 实现一个远程接口,例如
PersonController
,包含一个远程方法queryName
。 - 创建该接口的具体实现类
PersonControllerImpl
,并在其中实现远程方法。 - 在服务器的
main
方法中,实例化远程对象,创建RMI注册表,并使用Naming.rebind
将远程对象绑定到指定名称。
客户端
- 通过
Naming.lookup
方法,使用RMI注册表提供的名称获取远程对象的存根。 - 调用存根上的方法,就像调用本地方法一样,实际上是在调用服务器上的远程方法。
RMI的局限性
- 语言限制:RMI是Java特有的技术,不能直接用于非Java应用程序。
- 安全性问题:RMI的序列化机制可能带来安全风险,不建议将1099端口暴露在公网上。
- 性能和扩展性:RMI的性能受网络延迟和带宽影响,且在高并发情况下可能面临扩展性限制。
RMI的应用场景
RMI适用于需要Java程序之间进行远程通信的场景,如分布式银行系统、游戏服务器、股票交易系统和网上商城等。接下来一起看一个简单的案例使用吧。
二、案例使用
先来搞一个简单的Java RMI服务器端和客户端的实现案例。这个案例中,服务器端将提供一个名为HelloWorld
的远程服务,客户端将调用这个服务并打印返回的问候语。
服务器端实现
- 定义远程接口:
服务器和客户端都需要这个接口。它必须继承自java.rmi.Remote
接口,并且所有远程方法都要声明抛出RemoteException
。
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloWorld extends Remote {
String sayHello() throws RemoteException;
}
- 实现远程接口:
创建一个实现了上述接口的类,并实现远程方法。
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
public class HelloWorldImpl extends UnicastRemoteObject implements HelloWorld {
protected HelloWorldImpl() throws RemoteException {
super();
}
@Override
public String sayHello() throws RemoteException {
return "Hello, World!";
}
}
- 设置RMI服务器:
创建一个主类来设置RMI服务器,绑定远程对象到RMI注册表。
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class HelloWorldServer {
public static void main(String[] args) {
try {
// 创建远程对象
HelloWorld helloWorld = new HelloWorldImpl();
// 获取RMI注册表的引用,并在指定端口上创建或获取注册表实例
LocateRegistry.createRegistry(1099);
// 将远程对象绑定到RMI注册表中,客户端可以通过这个名字访问远程对象
Naming.bind("rmi://localhost/HelloWorld", helloWorld);
System.out.println("HelloWorld RMI object bound");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}
}
客户端实现
- 调用远程服务:
客户端使用RMI注册表的名字来查找远程对象,并调用其方法。
import java.rmi.Naming;
import java.rmi.RemoteException;
public class HelloWorldClient {
public static void main(String[] args) {
try {
// 使用RMI注册表的名字查找远程对象
HelloWorld helloWorld = (HelloWorld) Naming.lookup("rmi://localhost/HelloWorld");
// 调用远程方法
String response = helloWorld.sayHello();
System.out.println("Response: " + response);
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
来详细解释吧
- 远程接口 (
HelloWorld
): 这是服务器和客户端之间通信的协议。它定义了可以被远程调用的方法。 - 远程对象实现 (
HelloWorldImpl
): 这是远程接口的一个实现。RMI调用实际上会调用这个实现中的方法。 - 服务器 (
HelloWorldServer
): 负责创建远程对象的实例,并将这个实例绑定到RMI注册表中。这样客户端就可以通过注册表的名字来访问这个对象。 - 客户端 (
HelloWorldClient
): 使用RMI注册表的名字来查找服务器上的远程对象,并调用其方法。
接下来就可以编译所有类文件,运行服务器端程序,确保RMI注册表已经启动(在某些Java版本中会自动启动),再运行客户端程序,搞定。注意一下哈,由于RMI使用Java序列化机制,因此客户端和服务器的类路径必须一致或兼容。
三、RMI 在分布式银行系统中的应用
接下来V哥要介绍业务场景下的应用了,拿在分布式银行系统中来说,我们可以使用RMI来实现不同银行分行之间的通信,例如,实现账户信息的查询、转账等操作。以下是一个简化的示例,其中包括两个基本操作:查询账户余额和执行转账,按步骤一步一步来吧。
步骤1: 定义远程接口
首先,定义一个远程接口BankService
,它将被各个分行实现以提供银行服务。
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface BankService extends Remote {
double getAccountBalance(String accountNumber) throws RemoteException;
boolean transferFunds(String fromAccount, String toAccount, double amount) throws RemoteException;
}
步骤2: 实现远程接口
接下来,实现这个接口来创建远程对象,这个对象将提供实际的银行服务。
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
public class BankServiceImpl extends UnicastRemoteObject implements BankService {
private Map<String, Double> accounts = new HashMap<>();
protected BankServiceImpl() throws RemoteException {
super();
// 初始化一些账户信息
accounts.put("123456789", 5000.00);
accounts.put("987654321", 1000.00);
}
@Override
public double getAccountBalance(String accountNumber) throws RemoteException {
return accounts.getOrDefault(accountNumber, 0.00);
}
@Override
public boolean transferFunds(String fromAccount, String toAccount, double amount) throws RemoteException {
if (accounts.containsKey(fromAccount) && accounts.get(fromAccount) >= amount) {
accounts.put(fromAccount, accounts.get(fromAccount) - amount);
accounts.merge(toAccount, amount, Double::sum);
return true;
}
return false;
}
}
步骤3: 设置RMI服务器
服务器端将创建BankService
的远程对象实例,并将其绑定到RMI注册表中。
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class BankServer {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099); // 创建RMI注册表
BankService bankService = new BankServiceImpl();
Naming.rebind("//localhost/BankService", bankService); // 绑定远程对象
System.out.println("BankService is ready for use.");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}
}
步骤4: 实现RMI客户端
客户端将使用RMI注册表的名字来查找远程对象,并调用其方法。
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class BankClient {
public static void main(String[] args) {
try {
BankService bankService = (BankService) Naming.lookup("//localhost/BankService");
System.out.println("Account balance: " + bankService.getAccountBalance("123456789"));
// 执行转账操作
boolean isTransferSuccess = bankService.transferFunds("123456789", "987654321", 200.00);
if (isTransferSuccess) {
System.out.println("Transfer successful.");
} else {
System.out.println("Transfer failed.");
}
// 再次查询余额
System.out.println("New account balance: " + bankService.getAccountBalance("123456789"));
} catch (RemoteException | NotBoundException e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
来详细解释一下
- 远程接口 (
BankService
): 定义了两个方法:getAccountBalance
用于查询账户余额,transferFunds
用于执行转账操作。 - 远程对象实现 (
BankServiceImpl
): 实现了BankService
接口。它使用一个HashMap
来模拟账户和余额信息。 - 服务器 (
BankServer
): 设置了RMI服务器,将BankService
的实现绑定到RMI注册表中,供客户端访问。 - 客户端 (
BankClient
): 查找RMI注册表中的BankService
服务,并调用其方法来查询余额和执行转账。
撸完代码后,编译所有类文件,运行服务器端程序BankServer
,再运行客户端程序BankClient
,测试效果吧。
最后
最后V哥要提醒一下,在实际的银行系统中,当然还需要考虑安全性、事务性、持久性以及错误处理等多方面的因素,RMI的网络通信也需要在安全的网络环境下进行,以防止数据泄露或被篡改。你在应用中是怎么使用 RMI 的,欢迎关注威哥爱编程,一起交流一下哈。
Java RMI技术详解与案例分析的更多相关文章
- Vue基础语法-数据绑定、事件处理和扩展组件等知识详解(案例分析,简单易懂,附源码)
前言: 本篇文章主要讲解了Vue实例对象的创建.常用内置指令的使用.自定义组件的创建.生命周期(钩子函数)等.以及个人的心得体会,汇集成本篇文章,作为自己对Vue基础知识入门级的总结与笔记. 其中介绍 ...
- java RMI原理详解
java本身提供了一种RPC框架——RMI(即Remote Method Invoke 远程方法调用),在编写一个接口需要作为远程调用时,都需要继承了Remote,Remote 接口用于标识其方法可以 ...
- Ajax详解及其案例分析------如何获得Ajax对象,使用Ajax对象发送GET和POST请求,校验用户名,POST和GET请求时的乱码处理,实现级联的下拉列表
本节主要内容预览: 1 获得Ajax对象 2 使用Ajax对象发送GET请求 3 使用Ajax对象发送POST请求 4 使用Ajax校验用户名 5 POST请求时的乱码处理 6 GET请求时的乱码处理 ...
- 干货|java缓存技术详解
一.缓存是什么? 请点击此处输入图片描述 Cache ①高速缓冲存储器,其中复制了频繁使用的数据以利于快速访问. ②位于速度相差较大的两种硬件/软件之间,用于协调两者数据传输速度差异的结构 二.缓存有 ...
- java反射技术详解
反射: 其实就是动态的从内存加载一个指定的类,并获取该类中的所有的内容. 反射的好处:大大的增强了程序的扩展性. 反射的基本步骤: 1. 获得Class对象,就是获取到指定的名称的字节码文件对象. 2 ...
- Protocol Buffer技术详解(Java实例)
Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...
- 《Tomcat与Java Web开发技术详解》思维导图
越想构建上层建筑,就越觉得底层基础很重要.补课系列. 书是良心书,就是太基础了,正适合补课. [纯文字版] Tomcat与Java Web开发技术详解 Servlet Servlet的生命周期 初始化 ...
- Java基础-反射(reflect)技术详解
Java基础-反射(reflect)技术详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.类加载器 1>.JVM 类加载机制 如下图所示,JVM类加载机制分为五个部分 ...
- Comet技术详解:基于HTTP长连接的Web端实时通信技术
前言 一般来说,Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Ser ...
- SSE技术详解:一种全新的HTML5服务器推送事件技术
前言 一般来说,Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Ser ...
随机推荐
- 工程数学 实验5-MATLAB最优化工具箱的使用
(1)线性规划应用案例的求解 1.基本要求 通过一个农业生产计划优化安排的实例求解,培养学生解决实际线性规划问题的初步能力:熟悉线性规划的建模过程:掌握Matlab优化工具箱中线性规划函数的调用. 2 ...
- restful接口返回JSONObject和父类抽象实现类设计,请求头获取sign和支付宝RSA签名验签工具类方法
restful接口返回JSONObject和父类抽象实现类设计,请求头获取sign和支付宝RSA签名验签工具类方法 1.JSONObject可以通用数据的灵活性,类似Map数据,数据字段不清晰.具体返 ...
- HTML5画布-小球碰撞
Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` HTML5画布-小球碰撞 日期:2017-7-18 阿珏 ...
- iterrows()
iterrows() 是 Pandas 库中 DataFrame 对象的一个方法,它允许你迭代 DataFrame 的行.当你有一个 DataFrame 并且想要逐行访问数据(或者基于每一行的数据做一 ...
- 一些自托管(self hosted)服务的使用笔记
opengrok oracle的opengrok是一个项目代码查找工具,自建索引,类似工具有source insight 官方已经提供好了docker镜像,傻瓜式安装.不过增加新的项目源码时需要手动更 ...
- Linux 内核:设备树(1)dtb格式
Linux 内核:设备树(1)dtb格式 背景 dtb作为二进制文件被加载到内存中,然后由内核读取并进行解析,如果对dtb文件的格式不了解,那么在看设备树解析相关的内核代码时将会寸步难行,而阅读源代码 ...
- 【论文阅读】End-to-End Model-Free Reinforcement Learning for Urban Driving Using Implicit Affordances
文章名:CVPR2020: End-to-End Model-Free Reinforcement Learning for Urban Driving Using Implicit Affordan ...
- helloworld - 程序员的第一个社区终于来了
helloworld - 程序员的第一个社区终于来了 csdn事件 CSDN旗下的GitCode最近因为一种极其不道德的行为引起了开发者的广泛愤怒和抗议.CSDN在没有通知或征求开发者同意的情况下,悄 ...
- Unicode 和JS中的字符串
计算机内部使用二进制存储数据,只认识0和1两个数字,计算机的世界只有0和1.但我们的世界却充满着文字,如a, b, c.怎样才能让计算机显示文字,供我们使用和交流?只能先把文字转化成数字进行存储,然后 ...
- IntersectionObserver 实现图片懒加载
背景 最近使用express做导航类型网站,因为这个是后端jade渲染,浏览器拿到页面之后,解析出来dom结构,导致100+的图片瞬间加载,严重浪费了宽带资源,加重服务器负担,因此打算延迟加载图片 模 ...