Java RMI 介绍

简介: 一、Java RMI 概览Java RMI 指的是远程方法调用 (Remote Method Invocation)。它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法。所以,RMI相关对象必须运行在Java虚拟机中。在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对

一、Java RMI 概览

Java RMI 指的是远程方法调用 (Remote Method Invocation)。它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法。所以,RMI相关对象必须运行在Java虚拟机中。

在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象,供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展 java.rmi.Remote的接口)中指定的这些方法才可远程使用。

二、关于远程接口、对象和方法

在RMI中,当对象在虚拟机之间传递时,接收端虚拟机并不是复制一个远程对象,而是通过RMI接收一个远程对象的stub。这个stub就是对远程对象的引用,它其实是一个对远程对象的代理。接收端在本地调用了stub的方法,也就相当于调用了远程对象的方法。

接收端持有的远程对象的stub和远程对象实现的必须是同一组接口,这样才能将stub转换成远程对象接口类型,从而实现通过stub调用远程对象的方法。

三、编写一个RMI的步骤

1. 定义一个远程接口,此接口需要继承Remote,接口中的方法需要抛出一个`RemoteException`
2. 开发远程接口的实现类,这个类必须继承`UnicastRemoteObject`
3. 创建一个server并把远程对象注册到端口
4. 创建一个client查找远程对象,调用远程方法

下面我们就按照以上四步写一个分布式的Hello Word的例子来演示一下这个过程

3.1 定义一个远程接口

编写RMI应用的第一步就是先定义远程接口。远程接口必须继承java.rmi.Remote接口,并且声明自己的远程方法。为了处理远程方法发生的各种异常,每一个远程方法必须抛出一个java.rmi.RemoteException异常。

public interface HelloService extends Remote{
    String sayHello(String name) throws RemoteException;
}

这个远程接口只定义了一个远程方法 sayHello(),远程方法在调用的时候有可能失败比如发生网络问题或者server挂掉,此时远程方法会抛出RemoteException异常。

3.2 开发接口的实现类

开发接口的实现类,即具体的远程对象,在远程对象中实现远程接口中定义的方法。

public class HelloServiceImpl implements HelloService{
    public String sayHello(String name) {
        return "hello:"+name;
    }
}

3.3 创建一个Server并把对象注册到端口

在server端只需要做两件事:

1. 创建并导出远程对象
2. 用Java RMI registry 注册远程对象


public class RMIServer {
    public static void main(String[] args) {
        try {
            HelloService helloService= new HelloServiceImpl();
            HelloService stub=(HelloService) UnicastRemoteObject.exportObject(helloService,0);
            Registry registry=LocateRegistry.createRegistry(1099);
            registry.bind( "helloword", stub);
            System. out.println( "绑定成功!");
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

关于创建和导出远程对象

HelloService helloService= new HelloServiceImpl();
HelloService stub=(HelloService) UnicastRemoteObject.exportObject(helloService,0);

Server端的main方法在创建一个远程对象来提供服务时,此远程对象必须被导出才能被远程调用者调用。静态方法UnicastRemoteObject.exportObject()负责导出我们定义好的远程对象,它的第一个参数就是要到导出的远程对象,第二个参数是接收远程调用的tcp端口,这个值是0,它代表任意tcp端口。

exportObject()还会返回一个导出的远程对象的存根,注意,前面讲过了这个stub的类型必须和远程对象的接口类型一致,因为这个存根是要发送给client端进行调用。同时这个方法还会抛出一个RemoteException检查异常。

exportObject()方法被执行后,运行时会在一个新的Server Socket或共享Server Socket上进行监听,来接收对远程对象的远程调用。返回的存根对象和远程对象继承的是同一套remote接口,并且还它还包含了供client端口访问的主机IP和端口信息。

用Java RMI registry注册远程对象

 Registry registry=LocateRegistry.createRegistry(1099);
 registry.bind( "helloword", stub);

前面导出了远程对象的stub,它必须还能被client端找到并调用。为此,Java RMI 提供了 Registry API 可以允许应用程序把一个名称和远程对象的存根绑定在一起,这样client就可以通过这个绑定的名称很方便的查找到需要调用的远程对象了。

一旦远程对象在server端导出并注册,client就可以通过绑定的名称获得远程对象的引用,然后调用远程方法。

静态方法Registry registry=LocateRegistry.createRegistry(1099);会返回一个实现了java.rmi.registry.Registry接口的存根,并且在服务器本机的端口(默认是1099)上进行注册,返回的registry存根通过调用bind()方法在registry中把一个字符串名称和远程对象存根绑定在一起。

3.4 创建一个client查找远程对象,调用远程方法

public class RMIClient {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry();
            HelloService hello = (HelloService) registry.lookup( "helloword");
            String ret = hello.sayHello("heaven");
            System. out.println( ret);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (NotBoundException e) {
            e.printStackTrace();
        }
    }
}

客户端首先通过LocateRegistry.getRegistry("localhost")方法获得registry的存根,然后再执行registry存根的lookup()方法从服务器registry中获得远程对象的存根,最后客户端在远程对象存根上执行sayHello()方法。整个过程:

  • 客户端通过远程对象存根中的IP和端口打开一个服务器连接,并且序列化请求数据
  • 服务器端接收请求并且转发请求到远程对象调用服务方法,并且序列化运行结果发送给客户端
  • 客户端接收数据反序列化,把最终结果返回给调用者

四、结果测试

启动server,然后再启动client,控制台打印:

Hello: heaven!

五、Java RMI中用到的设计模式

Java RMI中用到了经典的工厂模式,先介绍下Java RMI应用的一些角色:

  • server:生产各种远程对象
  • client:通过命名服务器rmiregistry获取远程对象的存根
  • rmiregistry:具体处理client与server的交流

下面这幅图演示了整个步骤,下图中先做如下假设:

  • 有两个远程服务接口可供client调用,Factory和Product接口
  • FactoryImpl类实现了factory接口,ProductImpl类实现了Product接口

这里写图片描述

1. FactoryImpl被注册到了rmiregistry中
2. client端请求一个Factory的引用
3. rmiregistry返回client端一个FactoryImpl的引用
4. client端调用FactoryImpl的远程方法请求一个ProductImpl的远程引用
5. FactoryImpl返回给client端一个ProductImpl引用
6. client通过ProductImpl引用调用远程方法
目录
相关文章
|
数据可视化 大数据 定位技术
I+关系网络分析发布,提供完整的可视化分析和关系引擎功能
I+关系网络分析是以OLP模型为核心,面向业务快速建模,为开发者和终端用户提供大数据关系计算引擎(含API服务)和可视化交互分析能力,面向安防、关税、银行、保险、互联网等提供的产品化方案。目前,I+关系网络分析已在阿里巴巴、蚂蚁金服集团内广泛应用于反欺诈、反作弊、反洗钱等风控业务。
4711 0
|
JavaScript 内存技术
node与npm版本对应关系以及使用nvm管理node版本
node与npm版本对应关系以及使用nvm管理node版本
7164 0
|
存储 人工智能 自然语言处理
多模态RAG:三步构建图文并茂的智能问答、电商导购助手
本文介绍了如何使用OpenSearch LLM智能问答版,三步搭建一站式多模态RAG系统。
1746 9
|
机器学习/深度学习 数据采集 前端开发
【Python机器学习专栏】模型泛化能力与交叉验证
【4月更文挑战第30天】本文探讨了机器学习中模型泛化能力的重要性,它是衡量模型对未知数据预测能力的关键。过拟合和欠拟合影响泛化能力,而交叉验证是评估和提升泛化能力的有效工具。通过K折交叉验证等方法,可以发现并优化模型,如调整参数、选择合适模型、数据预处理、特征选择和集成学习。Python中可利用scikit-learn的cross_val_score函数进行交叉验证。
817 0
|
前端开发 开发者 UED
深入理解CSS属性“flex:1”的奥秘
深入理解CSS属性“flex:1”的奥秘
|
存储 安全 Java
构建Java镜像的10个最佳实践
构建Java镜像的10个最佳实践
构建Java镜像的10个最佳实践
|
安全 网络安全 数据安全/隐私保护
安全漏洞问题2:传输层保护不足
传输层保护不足:WEB应用程序在进行数据传输时,可能会选择HTTP或者HTTPS。对于前者,在传输的过程中,对传输数据并未做保护。在传输过程中,传输数据有可能被恶意用户截获、篡改。对于未使用SSL/TSL保护的传输通道,我们称之为传输层层保护不足。
4562 0
|
9天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1197 4
|
8天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
1116 87

热门文章

最新文章