RMI(Remote Method Invocation)是JAVA早期版本(JDK 1.1)提供的分布式应用解决方案,它作为重要的API被广泛的应用在EJB中。随着互联网应用的发展,分布式处理任务也随之复杂起 来,WebService也得到普遍的推广和应用。
在某些方面,例如跨语言平台的分布式应用,RMI就显得力不从心了。在实际的应用中,是采用WebService还是传统的RMI来实现?这是一个需要权衡的问题,两者的比较如下所述:
1. 比起WebService,它只能使用(调用)由JAVA编写的远程服务。而WebService是跨语言平台的,只要客户端和服务端双方都遵守SOAP规范即可;
2. RMI是在TCP协议基础上传递可序列化的java对象(字节数据),而WebService是在HTTP协议基础上通过XML来传输数据的。因此,在同等业务数据量的前提下,RMI的效率要高于WebService。
因此,RMI可以用在业务结构比较简单,而要求实时高效的分布式应用中。
从设计角度上讲,JAVA采用的是三层结构模式来实现RMI。在整个体系结构中,有如下几个关键角色构成了通信双方:
1.客户端:
1)桩(StubObject):远程对象在客户端上的代理;
2)远程引用层(RemoteReference Layer):解析并执行远程引用协议;
3)传输层(Transport):发送调用、传递远程方法参数、接收远程方法执行结果。
2.服务端:
1)骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值;
2)远程引用层(Remote ReferenceLayer):处理远程引用语法之后向骨架发送远程方法调用;
3)传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层。
3.注册表(Registry):以URL形式注册远程对象,并向客户端回复对远程对象的引用。
图1 RMI三层模型
在实际的应用中,客户端并没有真正的和服务端直接对话来进行远程调用,而是通过本地JVM环境下的桩对象来进行的。
1.远程调用过程:
1)客户端从远程服务器的注册表中查询并获取远程对象引用。当进行远程调用时,客户端首先会与桩对象(Stub Object)进行对话,而这个桩对象将远程方法所需的参数进行序列化后,传递给它下层的远程引用层(RRL);
2)桩对象与远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际上是由相应的桩对象代理完成的。远程引用层在将桩的本地引用转换为服务器上对象的远程引用后,再将调用传递给传输层(Transport),由传输层通过TCP协议发送调用;
3)在服务器端,传输层监听入站连接,它一旦接收到客户端远程调用后,就将这个引用转发给其上层的远程引用层;
4)服务器端的远程引用层将客户端发送的远程应用转换为本地虚拟机的引用后,再将请求传递给骨架(Skeleton);
5)骨架读取参数,又将请求传递给服务器,最后由服务器进行实际的方法调用。
2.结果返回过程:
1)如果远程方法调用后有返回值,则服务器将这些结果又沿着“骨架->远程引用层->传输层”向下传递;
2)客户端的传输层接收到返回值后,又沿着“传输层->远程引用层->桩”向上传递,然后由桩来反序列化这些返回值,并将最终的结果传递给客户端程序。
又从技术的角度上讲,有如下几个主要类或接口扮演着上述三层模型中的关键角色:
1)注册表:java.rmi.Naming和ava.rmi.Registry;
2)骨架:java.rmi.remote.Skeleton
3)桩:java.rmi.server.RemoteStub
4)远程引用层:java.rmi.server.RemoteRef和sun.rmi.transport.Endpoint;
5)传输层:sun.rmi.transport.Transport
作为一般的RMI应用,JAVA为我们隐藏了其中的处理细节,而让开发者有更多的精力和时间花在实际的应用中。开发RMI的步骤如下所述:
1.服务端:
1)定义Remote子接口,在其内部定义要发布的远程方法,并且这些方法都要Throws RemoteException;
2)定义远程对象的实现类,通常有两种方式:
a. 继承UnicastRemoteObject或Activatable,并同时实现Remote子接口;
b. 只实现Remote子接口和java.io.Serializable接口。
3)编译桩(在JAVA 1.5及以后版本中,如果远程对象实现类继承了UnicastRemoteObject或Activatable,则无需此步,由JVM自动完成。否则需手工利用rmic工具编译生成此实现类对应的桩类,并放到和实现类相同的编译目录下);
4)启动服务器:依次完成注册表的启动和远程对象绑定。另外,如果远程对象实现类在定义时没有继承UnicastRemoteObject或Activatable,则必须在服务器端显示的调用UnicastRemoteObject类中某个重载的exportObject(Remote remote)静态方法,将此实现类对象导出成为一个真正的远程对象。
2.客户端:
1)定义用于接收远程对象的Remote子接口,只需实现java.rmi.Remote接口即可。但要求必须与服务器端对等的Remote子接口保持一致,即有相同的接口名称、包路径和方法列表等。
2)通过符合JRMP规范的URL字符串在注册表中获取并强转成Remote子接口对象;
3)调用这个Remote子接口对象中的某个方法就是为一次远程方法调用行为。
下面结合一个例子来说明RMI分布式应用的开发步骤。
背景:远程系统管理接口SystemManager允许客户端远程调用其内部的某个方法,来获取服务器环境下某个属性的值。因此,第一步需要在服务端和与之通信的客户端环境下,定义一个完全一样的SystemManager接口,将此接口标记为远程对象。
1.在服务端和客户端定义对等Remote子接口(SystemManager)
package com.daniele.appdemo.rmi; import java.rmi.Remote; import java.rmi.RemoteException; import com.daniele.appdemo.test.domain.User; /** * <p>系统管理远程对象接口</p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @version 1.0.0, 2013-5-21 * @see * @since AppDemo1.0.0 */ public interface SystemManager extends Remote { /** * <p>发布的远程对象方法一:获取所有系统环境信息</p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @return * @throws RemoteException * @since AppDemo1.0.0 */ public String getAllSystemMessage() throws RemoteException; /** * <p>发布的远程对象方法二:获取指定的系统环境信息</p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @param properties:环境信息对应的属性名,多个名之间用逗号隔开 * @return * @throws RemoteException * @since AppDemo1.0.0 */ public String getSystemMessage(String properties) throws RemoteException; }
2.在服务端定义Remote子接口的实现类(SystemManagerImpl),即远程对象的实际行为逻辑。
package com.daniele.appdemo.rmi.server; import java.io.Serializable; import java.rmi.RemoteException; import java.rmi.server.RemoteServer; import java.rmi.server.ServerNotActiveException; import org.apache.log4j.Logger; import com.daniele.appdemo.rmi.SystemManager; import com.daniele.appdemo.test.domain.User; import com.daniele.appdemo.util.SystemUtils; /** * <p> * 系统管理远程对象实现类。有两种实现方式: * 1.继承UnicastRemoteObject或Activatable,并同时实现Remote子接口 * 2.只实现Remote子接口,这种方式灵活但比较复杂: * 1)要求此实现类必须实现java.io.Serializable接口; * 2)通过这种方式定义的实现类此时还不能叫做远程对象实现类, * 因为在服务器端绑定远程对象之前,还需要利用JDK提供的rmic工具 * 将此实现类手工编译生成对应的桩实现类,并放到和它相同的编译目录下。 * </p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @version 1.0.0, 2013-5-21 * @see * @since AppDemo1.0.0 */ public class SystemManagerImpl implements SystemManager, Serializable { //public class SystemManagerImpl extends UnicastRemoteObject implements SystemManager { private static final long serialVersionUID = 9128780104194876777L; private static final Logger logger = Logger.getLogger(SystemManagerImpl.class); private static SystemManagerImpl systemManager = null; /** * <p>在服务端本地的匿名端口上创建一个用于监听目的的UnicastRemoteObject对象</p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @throws RemoteException * @since AppDemo1.0.0 */ private SystemManagerImpl() throws RemoteException { super(); // 在控制台中显示远程对象被调用,以及返回结果时产生的日志 RemoteServer.setLog(System.out); } public static SystemManagerImpl getInstance() throws RemoteException { if (systemManager == null) { synchronized (SystemManagerImpl.class) { if (systemManager == null) systemManager = new SystemManagerImpl(); } } return systemManager; } public String getAllSystemMessage() throws RemoteException { try { /* * getClientHost()方法可以获取触发当前远程方法被调用时的客户端的主机名。 * 在远程服务端的环境中,如果当前线程实际上没有运行客户端希望调用的远程方法时, * 则会抛出ServerNotActiveException。 * 因此,为了尽量避免这个异常的发生,它通常用于远程方法的内部实现逻辑中, * 以便当此方法真正的被调用时,可以记录下哪个客户端在什么时间调用了这个方法。 */ logger.info("Client {" + RemoteServer.getClientHost() + "} invoke method [getAllSystemMessage()]" ); } catch (ServerNotActiveException e) { e.printStackTrace(); } return SystemUtils.formatSystemProperties(); } public String getSystemMessage(String properties) throws RemoteException { try { logger.info("Client {" + RemoteServer.getClientHost() + "} invoke method [getAllSystemMessage(String properties)]"); } catch (ServerNotActiveException e) { e.printStackTrace(); } return SystemUtils.formatSystemProperties(properties.split(",")); } }
3.由于SystemManagerImpl 不是通过继承UnicastRemoteObject 或 Activatable来实现的,因此在服务器端需要利用JDK提供的rmic工具编译生成对应的桩类。否则,此步略过。例如,在Windows环境下打开命令控制台后
1)进入工程根目录 :
cd d:\ Development\AppDemo
2)桩编译:
cmic -classpath WebContent\WEB-INF\classes com.daniele.appdemo.rmi.server.SystemManagerImpl
语法格式为:
cmic -classpath <远程对象实现类bin目录的相对路径> <远程对象实现类所在的包路径>.<远程对象实现类的名称>
完成后,rmic将在相对于根目录的com\daniele\appdemo\rmi\server子目录中自动生成SystemManagerImpl_Stub桩对象类 (即“远程对象实现类名称_Stub”) 的编译文件,此时需要再将此编译文件拷贝到与远程对象实现类SystemManagerImpl相同的编译目录(\WebContent\WEB-INF\classes\com\daniele\appdemo\rmi\server)中,否则在服务器端发布远程对象时将会抛出java.rmi.StubNotFoundException。如下图所示。
图2 实现类与桩
需要特别注意的是:如果服务端中的远程对象实现类存在有对应的桩对象类编译文件,则要求在RMI客户端的环境中,也必须有这个对等的桩对象类编译文件,即意味着这个文件在两端有着相同的包路径、文件名和内部实现细节。因此,最简单的做法就是连同整个包(com\daniele\appdemo\rmi\server)在内,将图2中的SystemManagerImpl_Stub.class文件拷贝到RMI客户端工程的bin目录下即可。如下图。否则,当RMI客户端调用远程服务时将会抛出java.rmi.StubNotFoundException。
图3 RMI客户端工程下的对等桩文件
4.创建用于发布远程对象目的用的服务器(SystemManagerServer)
package com.daniele.appdemo.rmi.server; import java.io.IOException; import java.util.Arrays; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; import java.rmi.server.UnicastRemoteObject; import org.apache.log4j.Logger; import com.daniele.appdemo.rmi.SystemManager; /** * <p> * 系统管理远程服务器,它主要完成如下任务: * 1.在绑定之前先启动注册表服务。 * 2.将远程对象SystemManager绑定到注册表中,以便让客户端能远程调用这个对象所发布的方法; * </p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @version 1.0.0, 2013-5-21 * @see * @since AppDemo1.0.0 */ public class SystemManagerServer { private static final Logger logger = Logger.getLogger(SystemManagerServer.class); public static void main(String[] args) { try { SystemManager systemManager = SystemManagerImpl.getInstance(); /* * 如果远程对象实现类不是通过继承UnicastRemoteObject或Activatable来定义的, * 则必须在服务器端显示的调用UnicastRemoteObject类中某个重载的exportObject(Remote remote)静态方法, * 将此实现类的某个对象导出成为一个真正的远程对象。否则,此步省略。 */ UnicastRemoteObject.exportObject(systemManager); int port = 9527; String url = "rmi://localhost:" + port + "/"; String remoteObjectName = "systemManager"; /* * 在服务器的指定端口(默认为1099)上启动RMI注册表。 * 必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上。 */ LocateRegistry.createRegistry(port); /* * 将指定的远程对象绑定到注册表中: * 1.如果端口号不为默认的1099,则绑定时远程对象名称中必须包含Schema, * 即"rmi://<host或ip><:port>/"部分,并且Schema里指定的端口号应与createRegistry()方法参数中的保持一致 * 2.如果端口号为RMI默认的1099,则远程对象名称中不用包含Schema部分,直接定义名称即可 */ if (port == 1099) Naming.rebind(remoteObjectName, systemManager); else Naming.rebind(url + remoteObjectName, systemManager); logger.info("Success bind remote object " + Arrays.toString(Naming.list(url))); } catch (IOException e) { e.printStackTrace(); } } }
5.创建发出远程调用请求的客户端(SystemManagerClient)
package com.daniele.appdemo.rmi.client; import java.io.IOException; import java.rmi.Naming; import java.rmi.NotBoundException; import com.daniele.appdemo.rmi.SystemManager; import com.daniele.appdemo.test.domain.User; /** * <p>系统管理进行远程调用的客户端</p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @version 1.0.0, 2013-5-21 * @see * @since AppDemo1.0.0 */ public class SystemManagerClient { public static void main(String[] args) { /* * RMI URL格式:Schame/<远程对象名> * 1.Schame部分由"rmi://<server host或IP>[:port]"组成, * 如果远程对象绑定在服务端的1099端口(默认)上,则port部分可以省略,否则必须指定。 * 2.URL最后一个"/"后面的值为远程对象绑定在服务端注册表上时定义的远程对象名, * 即对应Naming.rebind()方法第一个参数值中最后一个"/"后面的值 */ String rmi = "rmi://192.168.1.101:9527/systemManager"; try { /* * 根据URL字符串查询并获取远程服务端注册表中注册的远程对象, * 这里返回的是本地实现了Remote接口的子接口对象(Stub), * 它与服务端中的远程对象具有相同的接口和方法列表,因而作为在客户端中远程对象的一个代理。 */ SystemManager systemManager = (SystemManager) Naming.lookup(rmi); // System.out.println(systemManager.getAllSystemMessage()); System.out.println(systemManager.getSystemMessage("java.version,os.name")); } catch (IOException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } } }
完成后,再依次到服务器和客户端上启动SystemManagerServer和SystemManagerClient即可。
相关推荐
基于Java—RMI分布式技术的应用研究 1. RMI概述 RMI(Remote Method Invocation)是Java语言中的一种分布式技术,允许在不同的地址空间中通信的对象之间进行远程调用。RMI提供了一种机制,使服务器端和客户端之间...
这个实例将带你深入理解Java RMI的原理和实际应用。 一、RMI基本概念 1. 远程接口:远程接口定义了可以在远程对象上调用的方法。这些接口需要继承`java.rmi.Remote`接口,并声明可能会抛出`java.rmi....
### Java RMI 分布式编程心得详解 #### 一、Java RMI 分布式编程概述 Java远程方法调用(Remote Method Invocation, RMI)是一种让位于不同Java虚拟机(Java Virtual Machine, JVM)上的对象能够互相调用彼此方法...
在这个"EX3_RMI分布式议程服务_principalk7z_"项目中,我们看到的是一个利用RMI构建的分布式议程服务。这样的服务可以让多个用户跨网络共享和管理他们的会议日程。 首先,我们要理解RMI的基本原理。RMI允许一个Java...
在这个"Java RMI分布式编程实例"中,我们将深入探讨RMI的基本概念、工作原理以及如何通过实例来掌握其关键特性。 1. **RMI基本概念** - **远程接口(Remote Interface)**:定义了远程对象的方法签名,它是客户端...
"基于Java RMI的分布式数据库系统开发与...本资源提供了一个详细的分布式数据库系统开发指南,涵盖了Java RMI、分布式数据库系统、分布式系统等相关知识点,为读者提供了一个系统的了解分布式数据库系统的方法和应用。
### RMI分布式应用实例解析 #### 一、RMI简介及分布式应用原理 RMI (Remote Method Invocation) 是 Java 提供的一种远程方法调用技术,它允许开发者在不同的 JVM 上像调用本地方法那样调用远程方法。RMI 的主要...
**RMI分布式会议系统**是一种基于Java Remote Method Invocation (RMI) 技术构建的软件应用,用于在分布式环境中实现高效的会议管理和协作。RMI允许Java对象在不同的JVM(Java虚拟机)之间进行交互,使得多台计算机...
RMI是Enterprise JavaBeans的核心技术之一,同时也是构建分布式Java应用程序的一个便捷途径。它的使用非常直观,但其内部机制却十分强大。 #### RMI的关键特性 RMI的核心理念之一是接口定义与其实现分离。这意味着...
Java远程方法调用(Java RMI)是一种允许在不同Java虚拟机(JVM)上运行的对象之间进行通信的机制。通过RMI,一个Java虚拟机...随着分布式计算的发展,RMI和其他远程通信技术将继续在构建现代应用程序中发挥关键作用。
在深入探讨Java分布式应用和面向服务架构(SOA)之前,我们首先需要理解这两个概念的基本含义及其在现代IT系统中的重要性。 #### Java分布式应用 Java分布式应用是指利用Java编程语言构建的、运行在多台计算机上的...
总之,RMI是Java中构建分布式系统的一个重要工具,通过它,开发者可以轻松地创建能在多个JVM间协作的应用程序,实现数据的实时同步和远程调用。在给定的例子中,利用RMI实现的客户端数据同步功能,正是这种技术优势...
在IT领域,分布式系统是构建大规模、高可用性应用程序的关键技术...通过这个项目,开发者可以深入理解RMI的工作原理,以及如何使用它来构建分布式系统。同时,这也是学习Java网络编程和分布式服务设计的一个实践案例。
总结来说,Java RMI为Java开发者提供了一种强大且易于使用的工具,用于构建分布式应用,它简化了对象在不同JVM之间的交互,并且能够与现有的系统进行集成,同时具备良好的安全性和性能。通过深入理解RMI的原理和特性...
学习和理解Java RMI对于开发分布式Java应用至关重要,它可以帮助你构建可扩展、高性能的应用程序,尤其适用于服务器集群和云环境。通过实践提供的"rmi"代码示例,你可以深入理解RMI的工作原理,并掌握如何在实际项目...
Java RMI(Remote Method Invocation)技术是Java平台中用于分布式计算的一种机制,它允许一个Java对象调用远程...通过这个项目,开发者可以学习到如何使用RMI创建分布式应用,以及如何处理并发和网络通信等问题。
本项目提供的“java RMI实现代码”包括客户端和服务器端的示例,通过清晰的代码注释帮助理解RMI的工作原理。 一、RMI的基本概念 1. 远程接口:远程接口定义了客户端和服务器之间通信的API,它是Java接口,标注了`@...
RMI是一种Java技术,它允许开发者创建分布式应用程序,其中Java对象可以在网络的不同节点之间进行通信。通过RMI,一个Java程序能够调用位于另一台计算机上的Java对象的方法,就好像它们在同一台机器上一样。这种能力...