写点什么

Java RMI 技术详解与案例分析

  • 2024-08-07
    福建
  • 本文字数:4420 字

    阅读完需:约 15 分钟

Java RMI(Remote Method Invocation)是一种允许 Java 虚拟机之间进行通信和交互的技术。它使得远程 Java 对象能够像本地对象一样被访问和操作,从而简化了分布式应用程序的开发。一些应用依然会使用 RMI 来实现通信和交互,今天的内容我们来聊聊 RMI 的那些事儿。


一、先来了解一下概念


RMI 原理

RMI 的基本思想是远程方法调用。客户端调用远程方法时,实际上是发送一个调用请求到服务器,由服务器执行该方法,并将结果返回给客户端。RMI 通过存根(Stub)和骨架(Skeleton)类来实现远程调用,存根位于客户端,而骨架位于服务器端。


RMI 组件

  1. 远程接口:必须继承自java.rmi.Remote接口,并声明抛出RemoteException

  2. 远程对象:实现了远程接口的类。

  3. RMI 服务器:提供远程对象,并处理客户端的调用请求。

  4. RMI 客户端:发起远程方法调用请求。

  5. 注册服务(Registry):提供服务注册与获取,类似于目录服务。


数据传递

RMI 使用 Java 序列化机制来传递数据。客户端将方法参数序列化后通过网络发送给服务器,服务器反序列化参数并执行远程方法,然后将结果序列化回传给客户端。


RMI 案例

以下是一个简单的 RMI 案例,包括服务器和客户端的实现思路,下文 V 将再用代码来解释:


服务器端

  1. 实现一个远程接口,例如PersonController,包含一个远程方法queryName

  2. 创建该接口的具体实现类PersonControllerImpl,并在其中实现远程方法。

  3. 在服务器的main方法中,实例化远程对象,创建 RMI 注册表,并使用Naming.rebind将远程对象绑定到指定名称。


客户端

  1. 通过Naming.lookup方法,使用 RMI 注册表提供的名称获取远程对象的存根。

  2. 调用存根上的方法,就像调用本地方法一样,实际上是在调用服务器上的远程方法。


RMI 的局限性

  • 语言限制:RMI 是 Java 特有的技术,不能直接用于非 Java 应用程序。

  • 安全性问题:RMI 的序列化机制可能带来安全风险,不建议将 1099 端口暴露在公网上。

  • 性能和扩展性:RMI 的性能受网络延迟和带宽影响,且在高并发情况下可能面临扩展性限制。


RMI 的应用场景

RMI 适用于需要 Java 程序之间进行远程通信的场景,如分布式银行系统、游戏服务器、股票交易系统和网上商城等。接下来一起看一个简单的案例使用吧。


二、案例使用


先来搞一个简单的 Java RMI 服务器端和客户端的实现案例。这个案例中,服务器端将提供一个名为HelloWorld的远程服务,客户端将调用这个服务并打印返回的问候语。


服务器端实现


  1. 定义远程接口


服务器和客户端都需要这个接口。它必须继承自java.rmi.Remote接口,并且所有远程方法都要声明抛出RemoteException

import java.rmi.Remote;import java.rmi.RemoteException;
public interface HelloWorld extends Remote { String sayHello() throws RemoteException;}
复制代码


  1. 实现远程接口

创建一个实现了上述接口的类,并实现远程方法。

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!"; }}
复制代码


  1. 设置 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(); } }}
复制代码


客户端实现


  1. 调用远程服务


客户端使用 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,测试效果吧。


文章转载自:威哥爱编程

原文链接:https://www.cnblogs.com/wgjava/p/18343209

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
Java RMI技术详解与案例分析_Java_不在线第一只蜗牛_InfoQ写作社区