参考文章:《Java RMI 简介、原理、实例 (远程接口|分布式部署)》
http://hi.baidu.com/wqlearner/blog/item/f3fbf78128f7589df703a690.html
《RMI原理及实现》
http://java.chinaitlab.com/rcj/1850.html
《JAVA RMI远程方法调用简单实例》
http://www.cnblogs.com/leslies2/archive/2011/05/20/2051844.html
本文的内容大部分摘自以上三篇文章,在此对文章的原作者说声谢谢。
一、Java RMI 简介
远程方法调用(Remote Method Invocation,RMI)是用Java在JDK1.1中实现的,它大大增强了Java开发分布式应用的能力。Java作为一种风靡一时的网络开发语言,其巨大的 威力就体现在它强大的开发分布式网络应用的能力上,而RMI就是开发百分之百纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是 RPC的Java版本。但是传统RPC并不能很好地应用于分布式对象系统。而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。
RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP是专为Java的远程对象制定的协议。因此,Java RMI具有Java的"Write Once,Run Anywhere"的优点,是分布式应用系统的百分之百纯Java解决方案。用Java RMI开发的应用系统可以部署在任何支持JRE(Java Run Environment Java,运行环境)的平台上。但由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非 Java语言书写的对象进行通信。
RMI(Remote Method Invocation)是远程方法调用的简称,象其名称暗示的那样,它能够帮助我们查找并执行远程对象的方法。通俗地说,远程调用就象将一个class放在A机器上,然后在B机器中调用这个class的方法。尽管RMI不是唯一的企业级远程对象访问方案,但它却是最容易实现的。与能够使不同编程语言开发的CORBA不同的是,RMI是一种纯Java解决方案。在RMI中,程序的所有部分都由Java编写。
RMI是一种远程方法调用机制,其过程对于最终用户是透明的:在程序运行时,如果我不说它使用了RMI,其他人不可能知道调用的方法存储在其他机器上。当然了,二台机器上必须都安装有Java虚拟机(JVM)。其他机器需要调用的对象必须被导出到远程注册服务器,这样才能被其他机器调用。因此,如果机器A要调用机器B上的方法,则机器B必须将该对象导出到其远程注册服务器。注册服务器是服务器上运行的一种服务,它帮助客户端远程地查找和访问服务器上的对象。一个对象只有导出来后,然后才能实现RMI包中的远程接口。例如,如果想使机器B中的HelloWorld对象能够被远程调用,它就必须实现远程接口。
RMI需要使用占位程序(Stub)和框架(Skeleton),占位程序在客户端,框架在服务器端。在调用远程方法时,我们无需直接面对存储有该方法的机器。占位程序(Stub)就象客户端机器上的一个本机对象,它就象服务器上的对象的代理,向客户端提供能够被服务器调用的方法。然后,占位程序(Stub)就会向服务器端的框架(Skeleton)发送方法调用,框架(Skeleton)就会在服务器端执行接收到的方法。
Stub和Skeleton之间通过远程调用层进行相互通讯,远程调用层遵循TCP/IP协议收发数据。下面我们来大致了解一种称为为“绑定”的技术。客户端无论何时要调用服务器端的对象,你可曾想过他是如何告诉服务器他想创建什么样的对象吗?
这正是“绑定”的的用武之地。在服务器端,我们将一个字符串变量与一个对象联系在一起(可以通过方法来实现),客户端通过将那个字符串传递给服务器来告诉服务器它要创建的对象,这样服务器就可以准确地知道客户端需要使用哪一个对象了。所有这些字符串和对象都存储在的远程注册服务器中。
二、RMI(远程方法调用) 的组成
远程对象:这个接口只定义了一个方法。我们应当明白的是,这个接口并非总是不包括方法的代码而只包括方法的定义。远程对象包含要导出的每个方法的定义,它还实现Java.rmi中的远程接口。
·远程对象实现:这是一个实现远程对象的类。如果实现了远程对象,就能够覆盖该对象中的所有方法,因此,远程对象的实现类将真正包含我们希望导出的方法的代码。
·远程服务器:这是一个作为服务器使用的类,它是相对于要访问远程方法的客户端而言的。它存储着绑定的字符串和对象。
·远程客户端:这是一个帮助我们访问远程方法提供帮助的类,它也是最终用户。我们将使用查找和调用远程方法的方法在该类中调用远程方法
三、 RMI(远程方法调用)的工作原理
RMI系统结构,在客户端和服务器端都有几层结构。
--------- ----------
| 客户 | | 服务器|
---------- ----------
| |
------------- ----------
| 占位程序 | | 框架---------------------
| 远 程 引 用 层 |
------------------------------------
| |
------------------------------------
| 传 输 层 |
------------------------------------
方法调用从客户对象经占位程序(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传 输层,向上穿过远程调用层和 框架 (Skeleton),到达服务器对象。 占位程序扮演着远程服务器对象的代理的角色,使该对象可被客户激活。 远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。传输层管理实际的连接,并且追追踪可以接受方法调用的远程对象。服 务器端的 框架 完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。
四、RMI的开发步骤
1.先创建远程接口及声明远程方法,注意这是实现双方通讯的接口,需要继承Remote
2.开发一个类来实现远程接口及远程方法,值得注意的是实现类需要继承UnicastRemoteObject
3.注册服务,启动远程对象
4.最后客户端查找远程对象,并调用远程方法
五、实例
1.HelloWorld实例:
(1)创建远程接口及声明远程方法(HelloWorld.java)
package com.ipi.rmi.test; import java.rmi.Remote; import java.rmi.RemoteException; public interface HelloWorld extends Remote { /** * 简单的返回“Hello World!"字样 * @return 返回“Hello World!"字样 * @throws java.rmi.RemoteException */ public String helloWorld() throws RemoteException; /** * 一个简单的业务方法,根据传入的人名返回相应的问候语 * @param someBodyName 人名 * @return 返回相应的问候语 * @throws java.rmi.RemoteException */ public String sayHelloToSomeBody(String someBodyName) throws RemoteException; }
(2). 实现远程接口及远程方法(继承UnicastRemoteObject)HelloWorldImpl.java
package com.ipi.rmi.test; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class HelloWorldImpl extends UnicastRemoteObject implements HelloWorld { private static final long serialVersionUID = 7260927179338137037L; /** * 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,且声明抛出RemoteException异常 * 这是为了强制派生类要遵守基类方法的异常说明,对象的可替换性得到了保证 * @throws RemoteException */ protected HelloWorldImpl() throws RemoteException { super(); } @Override public String helloWorld() throws RemoteException { return "Hello World"; } @Override public String sayHelloToSomeBody(String someBodyName) throws RemoteException { return "你好," + someBodyName + "!"; } }
(3). 启动RMI注册服务,并注册远程对象(HelloRmiServer.java)
package com.ipi.rmi.test; import java.net.MalformedURLException; import java.rmi.AlreadyBoundException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; public class HelloRmiServer { public static void main(String args[]) { try { // 创建一个远程对象 HelloWorld helloWorld = new HelloWorldImpl(); // 本地主机上的远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是1099),必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上 LocateRegistry.createRegistry(8888); // 把远程对象注册到RMI注册服务器上,并命名为helloWorld // 绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的) Naming.bind("rmi://localhost:8888/helloWorld", helloWorld); // Naming.bind("//localhost:8888/helloWorld",rhello); System.out.println(">>>>>INFO:远程HelloWorld对象绑定成功!"); } catch (RemoteException e) { System.out.println("创建远程对象发生异常!"); e.printStackTrace(); } catch (AlreadyBoundException e) { System.out.println("发生重复绑定对象异常!"); e.printStackTrace(); } catch (MalformedURLException e) { System.out.println("发生URL畸形异常!"); e.printStackTrace(); } } }
(4). 客户端查找远程对象,并调用远程方法(HelloRmiClient)
package com.ipi.rmi.test; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; public class HelloRmiClient { public static void main(String args[]){ try { //在RMI服务注册表中查找名称为helloWorld的对象 HelloWorld helloWorld =(HelloWorld) Naming.lookup("rmi://localhost:8888/helloWorld"); //rmi:前缀可以去掉 //HelloWorld helloWorld =(HelloWorld) Naming.lookup("//localhost:8888/helloWorld"); //当创建对象注册表时使用的是默认注册表默认端口时可以使用此方法查找名称为helloWorld的对象 // HelloWorld helloWorld = (HelloWorld) Naming.lookup("helloWorld"); System.out.println(helloWorld.helloWorld()); System.out.println(helloWorld.sayHelloToSomeBody("电子商务")); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } } }
(5). 执行程序:启动服务HelloRmiServer;运行客户端HelloRmiClient进行调用,控制台会打印出:
Hello World
你好,电子商务!
2.实体类的实例
(1)首先为服务建立一个实体对象,注意因为此对象需要现实进行远程传输,所以必须继承Serializable
package com.ipi.rmi.test; import java.io.Serializable; //注意对象必须继承Serializable public class PersonEntity implements Serializable { /** * */ private static final long serialVersionUID = 8129088460013147352L; private int id; private String name; private int age; public void setId(int id) { this.id = id; } public int getId() { return id; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } }
(2)创建远程接口PersonService,注意远程接口需要继承Remote
package com.ipi.rmi.test; import java.rmi.Remote; import java.rmi.RemoteException; import java.util.List; //此为远程对象调用的接口,必须继承Remote类 public interface PersonService extends Remote { public List<PersonEntity> GetList() throws RemoteException; }
(3)建立PersonServiceImpl实现远程接口,注意此为远程对象实现类,需要继承UnicastRemoteObject
package com.ipi.rmi.test; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.LinkedList; import java.util.List; //此为远程对象的实现类,须继承UnicastRemoteObject public class PersonServiceImpl extends UnicastRemoteObject implements PersonService { private static final long serialVersionUID = -4453013520188241623L; public PersonServiceImpl() throws RemoteException { super(); // TODO Auto-generated constructor stub } @Override public List<PersonEntity> GetList() throws RemoteException { // TODO Auto-generated method stub System.out.println("Get Person Start!"); List<PersonEntity> personList=new LinkedList<PersonEntity>(); PersonEntity person1=new PersonEntity(); person1.setAge(25); person1.setId(0); person1.setName("Leslie"); personList.add(person1); PersonEntity person2=new PersonEntity(); person2.setAge(25); person2.setId(1); person2.setName("Rose"); personList.add(person2); return personList; } }
(4)建立服务器端,在服务器端注册RMI通讯端口与通讯路径
package com.ipi.rmi.test; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; public class RmiServer { public static void main(String[] args) { try { PersonService personService=new PersonServiceImpl(); //注册通讯端口 LocateRegistry.createRegistry(6600); //注册通讯路径 Naming.rebind("rmi://127.0.0.1:6600/PersonService", personService); System.out.println("Service Start!"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
(5)最后建立客户端进行测试,注意客户调用的RMI路径必须服务器配置一致
package com.ipi.rmi.test; import java.rmi.Naming; import java.util.List; public class RmiClient { public static void main(String[] args){ try{ //调用远程对象,注意RMI路径与接口必须与服务器配置一致 PersonService personService=(PersonService)Naming.lookup("rmi://127.0.0.1:6600/PersonService"); List<PersonEntity> personList=personService.GetList(); for(PersonEntity person:personList){ System.out.println("ID:"+person.getId()+" Age:"+person.getAge()+" Name:"+person.getName()); } }catch(Exception ex){ ex.printStackTrace(); } } }
注意:远程调用的实体类一定是实现Serializable接口,否则会抛出如下所示的异常:
java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.ipi.rmi.test.PersonEntity
六、RMI(远 程方法调用)的优点
从最基本的角度看,RMI是Java的远程过程调用(RPC)机制。与传统的 RPC系统相比,RMI具有若干优点,因为它是Java面向对象方法的一部分。传统的RPC系 统采用中性语言,所以是最普通的系统--它们不能提供所有可能的目标平台所具有的功能。
RMI以Java为核心,可与采用本机方法 与现有系统相连接。这就是说,RMI可采用自然、直接和功能全面的方式为您提供分布式计算技术,而这种技术可帮助您以不断递增和无缝的方式为整个系统添加 Java功能。
RMI的主要优点如下:
面向对象:RMI可将完整的对象作为参数和返回值 进行传递,而不仅仅是预定义的数据类型。也就是说,您可以将类似Java哈希表这样的复 杂类型作为一个参数进行传递。而在目前的RPC系统中,您只能依靠客户机将此类对象分解成基本数据类型,然后传递这些数据类型,最后在服务器端重新创建哈 希表。RMI则不需额外的客户程序代码(将对象分解成基本数据类型),直接跨网传递对象。
可移动属性:RMI可将属性(类实现程序) 从客户机移动到服务器,或者从服务器移到客户机。例如,您可以定义一个检查雇员开支报告的接口,以便察看雇员是 袷亓斯 灸壳笆敌械恼 摺T诳 Пǜ娲唇ê螅 突Щ 突岽臃 衿鞫嘶竦檬迪指媒涌诘亩韵蟆H绻 叻⑸ 浠 衿鞫司突峥 挤祷厥褂昧诵抡 叩母媒涌诘 牧硪桓鍪迪殖绦颉D 槐卦谟没低成习沧叭魏涡碌娜砑 湍茉诳突Ф思觳橄拗铺跫?--从而向用户提供烁?快的反馈,并降低服务器的工作量。这样就能具备最 大的灵活性,因为政策改变时只需要您编写一个新的Java类,并将其在服务器主机上安装一次即可。
设计方式:对象传递功能使您可以在 分布式计算中充分利用面向对象技术的强大功能,如二层和三层结构系统。如果您能够传递属性,那么您就可以在您的解决方案中使用面向对象的设计方式。所有面 向对象的设计方式无不依靠不同的属性来发挥功能,如果不能传递完整的对象--包括实现和类型--就会失去设计方式上所提供的优点。
安 全:RMI使用Java内置的安全机制保证下载执行程序时用户系统的安全。RMI使用专门为保护系统免遭恶意小应用程序侵害而设计的安全管理程序,可 保护您的系统和网络免遭潜在的恶意下载程序的破坏。在情况严重时,服务器可拒绝下载任何执行程序。
便于编写和使用:RMI使得Java 远程服务程序和访问这些服务程序的Java客户程序的编写工作变得轻松、简单。远程接口实际上就是Java接口。服务程序大约用三行指令宣布本身是服务程 序,其它方面则与任何其它Java对象类似。这种简单方法便于快速编写完整的分布式对象系统的服务程序,并快速地制做软件的原型和早期版本,以便于进行测试和评估。因为RMI程序编写简单,所以维护也 简单。
可连接现有/原有的系统:RMI可通过Java的本机方法接口JNI与现有系统进行进行交互。利用RMI和JNI,您就能用 Java语言编写客户端程序,还能使用现有的服务器端程序。在使用RMI/JNI与现有服务器连接时,您可以有选择地用Java重新编写服务程序的任何部 分,并使新的程序充分发挥Java的功能。类似地,RMI可利用JDBC、在不修改使用数据库的现有非Java源代码的前提下与现有关系数据库进行交互。
编写一次,到处运行:RMI是Java“编写一次,到处运行 ”方法的一部分。任何基于RMI的系统均可100%地移植到任何Java虚拟机上,RMI/JDBC系统也 不例外。如果使用RMI/JNI与现有系统进行交互工作,则采用JNI编写的代码可与任何Java虚拟机进行编译、运行。
分布式垃圾 收集:RMI采用其分布式垃圾收集功能收集不再被网络中任何客户程序所引用的远程服务对象。与Java 虚拟机内部的垃圾收集类似,分布式垃圾收集功能允许用户根据自己的需要定义服务器对象,并且明确这些对象在不再被客户机引用时会被删除。
并行计算:RMI采用多线程处理方法,可使您的服务器利用这些Java线程更 好地并行处理客户端的请求。Java分布式计算解决方案:RMI从JDK 1.1开始就是Java平台的核心部分,因此,它存在于任何一台1.1 Java虚拟机中。所有RMI系统均采用相同的公开协议,所以,所有Java 系统均可直接相互对话,而不必事先对协议进行转换。
七:RMI与CORBA的关系
RMI 和 CORBA 常被视为相互竞争的技术,因为两者都提供对远程分布式对象的透明访问。但这两种技术实际上是相互补充的,一者的长处正好可以弥补另一者的短处。RMI 和 CORBA 的结合产生了 RMI-IIOP,RMI-IIOP 是企业服务器端 Java 开发的基础。
1997 年,IBM 和 Sun Microsystems启动了一项旨在促进 Java 作为企业开发技术的发展的合作计划。两家公司特别着力于如何将 Java 用作服务器端语言,生成可以结合进现有体系结构的企业级代码。所需要的就是一种远程 传输技术,它兼有 Java 的 RMI(Remote Method Invocation,远程方法调用)较少的资源占用量和更成熟的 CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)技术的健壮性。出于这一需要,RMI-IIOP问世了,它帮助将 Java 语言推向了目前服务器端企业开发的主流语言的领先地位。