Java的RMI远程方法调用实现和应用

简介:

最近在学习Dubbo,RMI是很重要的底层机制,RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制。使用这种机制,某一台计算机(即JVM虚拟机)上的对象可以调用另外一台计算机上的对象来获取远程数据。

RMI的实现对建立分布式Java应用程序至关重要,是Java体系非常重要的底层技术。

>>RMI的概念和原理

RMI思路是在客户端安装一个代理(proxy),代理是位于客户端虚拟机中的一个对象,对于客户端对象,看起来就像访问的远程对象一样,客户端代理会使用网络协议与服务器进行通信。
同样的服务端也会有一个代理对象来进行通信的繁琐工作。

在RMI中,客户端的代理对象被称为存根(Stub),存根位于客户端机器上,它知道如何通过网络与服务器联系。存根会将远程方法所需的参数打包成一组字节。对参数编码的过程被称为参数编组(parameter marshalling),参数编组的目的是将参数转换成适合在虚拟机之间进行传递的形式。在RMI协议中,对象时使用序列化机制进行编码的。

总的来说,客户端的存根方法构造了一个信息块,它由以下几部分组成
被使用的远程对象的标识符;
被调用的方法的描述;
编组后的参数;

然后,存根将此信息发送给服务器。在服务器的一端,接收器对象执行以下动作:
定位要调用的远程对象;
调用所需的方法,并传递客户端提供的参数;
捕获返回值或调用产生的异常;
将返回值编组,打包送回给客户端存根;
客户端存根对来自服务器端的返回值或异常进行反编组,其结果就成为了调用存根返回值。

>>RMI体系结构

桩/框架(Stub/Skeleton)层:客户端的桩和服务器端的框架;
远程引用(remote reference)层:处理远程引用行为
传送层(transport):连接的建立和管理,以及远程对象的跟踪

 

>>RMI与代理模式

RMI的实现是典型的代理模式思想。

(1)代理模式的UML图

代理模式为其他对象提供一种代理以控制对这个对象的访问,把调用者与被调用者分离开,由代理负责传递信息来完成调用。
比如你想买美版Iphone6s,朋友出国,帮你海淘带回,整个过程就是代理模式,朋友作为代理,代你完成你想进行的操作。

代理模式能将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度;
在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用,代理对象也可以对目标对象调用之前进行其他操作。
代理模式在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢;同时也增加了系统的复杂度。

(2)代理模式在RMI这种的体现

远程代理的内部机制是这样的:

下面的RMI实例,其实就是一个代理模式的应用。

>>RMI的简单实例

RMI的开发步骤如下:
先创建远程接口及声明远程方法,注意这是实现双方通讯的接口,需要继承Remote
开发一个类来实现远程接口及远程方法,值得注意的是实现类需要继承UnicastRemoteObject
通过javac命令编译文件,通过java -server 命令注册服务,启动远程对象
最后客户端查找远程对象,并调用远程方法

(1)服务端远程接口
创建远程接口SearchService,接口必须继承Remote类,每一个定义方法都要抛出RemoteException异常

1
2
3
4
5
6
import  java.rmi.Remote;
import  java.rmi.RemoteException;
 
public  interface  SearchService  extends  Remote {
     public  User findUser(String id)  throws  RemoteException;;
}

  

(2)建立SearchServiceImpl实现远程接口,注意此为远程对象实现类,需要继承UnicastRemoteObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import  java.rmi.RemoteException;
public  class  SearchServiceImpl  implements  SearchService{
 
     /**
      * 抛出RemoteException
      * @throws RemoteException
      */
     public  SearchServiceImpl()  throws  RemoteException {
         super ();
         }
     
     @Override
     public  User findUser(String id)  throws  RemoteException {
         /**
          * 模拟查找返回数据
          */
         User user= new  User(id, "Tom" , "18岁" );
         return  user;
     }
 
}

  

(3)为服务建立一个Model层,此对象需要远程传输,所以必须实现implements Serializable序列化接口,也就是可以在client和server端进行传输的可序列化对象
新建User类,用于数据传输:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public  class  User  implements  Serializable{
 
     private  static  final  long  serialVersionUID = 1L;
     
     private  String id;
     private  String name;
     private  String age;
     
     public  User(String id,String name,String age){
         this .id=id;
         this .name=name;
         this .age=age;
     }
     
     public  String getId() {
         return  id;
     }
 
     public  void  setId(String id) {
         this .id = id;
     }
 
     public  String getName() {
         return  name;
     }
 
     public  void  setName(String name) {
         this .name = name;
     }
 
     public  String getAge() {
         return  age;
     }
 
     public  void  setAge(String age) {
         this .age = age;
     }
     
     public  String toString(){
         StringBuffer sb= new  StringBuffer();
         sb.append( "~用户id-" +id);
         sb.append( "~用户姓名-" +id);
         sb.append( "~用户年龄-" +id);
         return  sb.toString();
     }
 
}

  

(4)建立服务器端,在服务器端注册RMI通讯端口与通讯路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import  java.net.MalformedURLException;
import  java.rmi.Naming;
import  java.rmi.RemoteException;
import  java.rmi.registry.LocateRegistry;
import  java.rmi.registry.Registry;
 
/**
  * @author BingYue
  */
public  class  Process {
     
     public  static  void  main(String[] args){
         
         try  {
             SearchService searchService= new  SearchServiceImpl();
             //注册通信端口
             Registry registry=LocateRegistry.createRegistry( 5678 );
             //注册通讯路径
             Naming.rebind( "rmi://127.0.0.1:5678/searchService" , searchService);
             System.out.println( "Service Start!" );
             
         }  catch  (RemoteException e) {
             e.printStackTrace();
         }  catch  (MalformedURLException e) {
             e.printStackTrace();
         }
     }
 
}

  

(5)创建客户端,导入上面的实体类和接口,通过Naming.lookup()的方式调用
如果使用IDE,就可以新建一个项目,Client代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import  java.rmi.Naming;
 
/**
  * @author BingYue
  */
public  class  Client {
     
     public  static  void  main(String[] args){
         try  {
             //调用远程对象,注意RMI路径与接口必须与服务器配置一致
             SearchService searchService=(SearchService)Naming.lookup( "rmi://127.0.0.1:5678/searchService" );
             User user=searchService.findUser( "100" );
             System.out.println(user.toString());
         }  catch  (Exception e) {
             e.printStackTrace();
         }
     }
}

  

(6)分别运行服务端和客户端,获得远程调用结果

>>RMI应用程序的基本模型

(1)Remote接口:是一个不定义方法的标记接口

1
Public  interface  Remote{}

在RMI中,远程接口声明了可以从远程Java虚拟机中调用的方法集。远程接口满足下列要求:
远程接口必须直接或间接扩展Java.rmi.Remote接口,且必须声明为public,除非客户端于远程接口在同一包中;
在远程接口中的方法在声明时,除了要抛出与应用程序有关的一场之外,还必须包括RemoteException(或它的超类,IOExcepion或Exception)异常;
在远程方法声明中,作为参数或返回值声明的远程对象必须声明为远程接口,而非该接口的实现类。

(2)RemoteObject抽象类实现了Remote接口和序列化Serializable接口,它和它的子类提供RMI服务器函数。

(3)LocateRegistry final()类用于获得特定主机的引导远程对象注册服务器程序的引用(即创建stub),或者创建能在特定端口接收调用的远程对象注册服务程序。

服务器端:向其他客户机提供远程对象服务

1
2
3
4
5
6
7
8
9
10
11
SomeService servcie=……; //远程对象服务
Registry registry=LocateRegisty.getRegistry(); //Registry是个接口,他继承了Remote,此方法返回本地主机在默认注册表端口 1099 上对远程对象 Registry 的引用。
getRegistry( int  port) 返回本地主机在指定 port 上对远程对象 Registry 的引用;
getRegistry(String host) 返回指定 host 在默认注册表端口  1099  上对远程对象 Registry 的引用;
getRegistry(String host,  int  port) 返回指定的 host 和 port 上对远程对象 Registry 的引用
 
registry.bind(“I serve”,service); // bind(String name,Remote obj) 绑定对此注册表中指定 name 的远程引用。name : 与该远程引用相关的名称 obj : 对远程对象(通常是一个 stub)的引用
unbind(String name)移除注册表中指定name的绑定。
rebind(String name,Remote obj)重新绑定,如果name已存在,但是Remote不一样则替换,如果Remote一样则丢弃现有的绑定
lookup(String name) 返回注册表中绑定到指定 name 的远程引用,返回Remote
String[] list() 返回在此注册表中绑定的名称的数组。该数组将包含一个此注册表中调用此方法时绑定的名称快照。

  

客户机端:向服务器提供相应的服务请求

1
2
3
Registry registry=LocateRegisty.getRegistry();
SomeService servcie=(SomeService)registry.lookup(“I serve”);
Servcie.requestService();

(4) Naming类和Registry类类似

客户端:

1
2
Naming.lookup(String url)
url 格式如下 "rmi://localhost/" +远程对象引用

服务器端:

1
2
Registry registry=LocateRegistry.createRegistry( int  port);
Naming.rebind(“service”,service);

(5) RMISecurityManager类

在RMI引用程序中,如果没有设置安全管理器,则只能从本地类路径加载stub和类,这可以确保应用程序不受由远程方法调用所下载的代码侵害
在从远程主机下载代码之前必须执行以下代码来安装RMISecurityManager:

1
System.setSecurityManager( new  RMISecurityManager());

  

>>RMI的主要应用

RMI的用途是为分布式Java应用程序之间的远程通信提供服务,提供分布式服务。
目前主要应用时封装在各个J2EE项目框架中,例如Spring封装了RMI技术。

(1)在Spring中实现RMI

在服务器端定义服务的接口,定义特定的类实现这些接口;
在服务器端使用org.springframework.remoting.rmi.RmiServiceExporter类来注册服务;
在客户端使用org.springframework.remoting.rmi.RmiProxyFactoryBean来实现远程服务的代理功能;
在客户端定义访问与服务器端服务接口相同的类

(2)在Dubbo中的应用

Dubbo支持不同的协议,其中RMI协议底层就是Java RMI的实现,
RMI协议的Invoker转为Exporter发生在RmiProtocol类的export方法,
通过Spring或Dubbo或JDK来实现RMI服务,通讯细节由JDK底层来实现,节省了不少工作量。


目录
相关文章
|
人工智能 算法 Java
Java与AI驱动区块链:构建智能合约与去中心化AI应用
区块链技术和人工智能的融合正在开创去中心化智能应用的新纪元。本文深入探讨如何使用Java构建AI驱动的区块链应用,涵盖智能合约开发、去中心化AI模型训练与推理、数据隐私保护以及通证经济激励等核心主题。我们将完整展示从区块链基础集成、智能合约编写、AI模型上链到去中心化应用(DApp)开发的全流程,为构建下一代可信、透明的智能去中心化系统提供完整技术方案。
669 3
|
存储 数据采集 搜索推荐
Java 大视界 -- Java 大数据在智慧文旅旅游景区游客情感分析与服务改进中的应用实践(226)
本篇文章探讨了 Java 大数据在智慧文旅景区中的创新应用,重点分析了如何通过数据采集、情感分析与可视化等技术,挖掘游客情感需求,进而优化景区服务。文章结合实际案例,展示了 Java 在数据处理与智能推荐等方面的强大能力,为文旅行业的智慧化升级提供了可行路径。
Java 大视界 -- Java 大数据在智慧文旅旅游景区游客情感分析与服务改进中的应用实践(226)
|
机器学习/深度学习 数据采集 数据可视化
Java 大视界 -- 基于 Java 的大数据可视化在城市空气质量监测与污染溯源中的应用(216)
本文探讨Java大数据可视化在城市空气质量监测与污染溯源中的创新应用,结合多源数据采集、实时分析与GIS技术,助力环保决策,提升城市空气质量管理水平。
Java 大视界 -- 基于 Java 的大数据可视化在城市空气质量监测与污染溯源中的应用(216)
|
存储 监控 数据可视化
Java 大视界 -- 基于 Java 的大数据可视化在企业生产运营监控与决策支持中的应用(228)
本文探讨了基于 Java 的大数据可视化技术在企业生产运营监控与决策支持中的关键应用。面对数据爆炸、信息孤岛和实时性不足等挑战,Java 通过高效数据采集、清洗与可视化引擎,助力企业构建实时监控与智能决策系统,显著提升运营效率与竞争力。
|
Java 大数据 数据处理
Java 大视界 -- 基于 Java 的大数据实时数据处理在工业互联网设备协同制造中的应用与挑战(222)
本文探讨了基于 Java 的大数据实时数据处理在工业互联网设备协同制造中的应用与挑战。文章分析了传统制造模式的局限性,介绍了工业互联网带来的机遇,并结合实际案例展示了 Java 在多源数据采集、实时处理及设备协同优化中的关键技术应用。同时,也深入讨论了数据安全、技术架构等挑战及应对策略。
|
数据采集 搜索推荐 Java
Java 大视界 -- Java 大数据在智能教育虚拟学习环境构建与用户体验优化中的应用(221)
本文探讨 Java 大数据在智能教育虚拟学习环境中的应用,涵盖多源数据采集、个性化推荐、实时互动优化等核心技术,结合实际案例分析其在提升学习体验与教学质量中的成效,并展望未来发展方向与技术挑战。
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
968 8
|
人工智能 Java API
Java与大模型集成实战:构建智能Java应用的新范式
随着大型语言模型(LLM)的API化,将其强大的自然语言处理能力集成到现有Java应用中已成为提升应用智能水平的关键路径。本文旨在为Java开发者提供一份实用的集成指南。我们将深入探讨如何使用Spring Boot 3框架,通过HTTP客户端与OpenAI GPT(或兼容API)进行高效、安全的交互。内容涵盖项目依赖配置、异步非阻塞的API调用、请求与响应的结构化处理、异常管理以及一些面向生产环境的最佳实践,并附带完整的代码示例,助您快速将AI能力融入Java生态。
1706 12
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
1786 1
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。