综述
Rmi自从JDK1.1就已经出现了。而对于为什么在JAVA的世界里需要一个这样 思想理念就需要看下:RMI问世由来。其实真正在国内使用到它的比较少,不过在前些年比较火的EJB就是在它的基础上进一步深化的。从本质上来讲RMI的兴起正是为了设计分布式的客户、服务器结构需求而应运而生的,而它的这种B/S结构思想能否和我们通常的JAVA编程更加贴切呢?言外之意就是能否让这种分布式的状态做到更加透明,作为开发人员只需要按照往常一样开发JAVA应用程序一样来开发分布式的结构。那现在的问题是如何来划平这个鸿沟呢?首先我们来分析下在JAVA世界里它的一些特点因素:
l JAVA使用垃圾收集确定对象的生命周期。
l JAVA使用异常处理来报告运行期间的错误。这里就要和我们网络通讯中的异常相联系起来了。在B/S结构的网络体系中我们的这种错误性是非常常见的。
l JAVA编写的对象通过调用方法来调用。由于网络通讯把我们的客户与服务器之间阻隔开了。但是代理的一种方式可以很好的提供一种这样的假象,让开发人员或者使用者都感觉是在本地调用。
l JAVA允许一种高级的使用类加载器(CLassLoader)机制提供系统类路径中没有的类。这话什么意思?
主要特点
上面说到了分布式的方式和我们的JAVA中如何更好的划平这个鸿沟,需要具备的特质。
那这里我们来看看我们所谓的RMI到底跟我们普通的JAVA(或者说JavaBean)存在一些什么样的差异:
l RMI远程异常(Remote Exception):在上面我们也提到了一个网络通讯难免有一些无论是软件级别的还是硬件级别的异常现象,有时候这些异常或许是一种无法预知的结果。让我们开发人缘如何来回溯这种异常信息,这个是我们开发人员要关心的。因此在调用远程对象的方法中我们必须在远程接口中(接口是一种规范的标准行为)所以在调用的这个方法体上需要签名注明:java.rmi,RemoteException.。这也就注明了此方法是需要调用远程对象的。
l 值传递 :当把对象作为参数传递给一个普通的JAVA对象方法调用时,只是传递该对象的引用。请注意这里谈到的是对象的“引用”一词,如果在修改该参数的时候,是直接修改原始对象。它并不是所谓的一个对象的备份或者说拷贝(说白了就是在本JVM内存中的对象)。但是如果说使用的是RMI对象,则完全是拷贝的。这与普通对象有着鲜明的对比。也正是由于这种拷贝的资源消耗造就了下面要说到的性能缺失了。
l 调用开销:凡是经过网络通讯理论上来说都是一种资源的消耗。它需要通过编组与反编组方式不断解析类对象。而且RMI本身也是一种需要返回值的一个过程定义。
l 安全性:一谈到网络通讯势必会说到如何保证安全的进行。
概念定义
在开始进行原理梳理之前我们需要定义清楚几个名词。对于这些名词的理解影响到后的深入进行。
1. Stub(存根,有些书上也翻译成:桩基在EJB的相关书籍中尤为体现这个意思):
这里举例说明这个概念起(或许不够恰当)。例如大家因公出差后,都有存在一些报销的发票或者说小票。对于你当前手头所拿到的发票并不是一个唯一的,它同时还在你发生消费的地点有一个复印件,而这个复印件就是所谓的存根。但是这个存根上并没有很多明细的描述,只是有一个大概的金额定义。它把很多的细节费用都忽略了。所以这个也是我们说的存根定义。而在我们RMI的存根定义就是使用了这样一个理解:在与远程发生通讯调用时,把通讯调用的所有细节都通过对象的封装形式给隐藏在后端。这本身就符合OOAD的意思理念。而暴露出来的就是我们的接口方式,而这种接口方式又和服务器的对象具有相同的接口(这里就和我们前面举例说的报销单据联系上了,报销单据的存根不知道会有一个什么形式发生具体问题,而你手执的发票具体就需要到贵公司去报销费用,而这里的公司财务处就是所谓的服务器端,它才是真正干实质性问题的。)因此作为开发人员只需要把精力集中在业务问题的解决上,而不需要考虑复杂的分布式计算。所有这些问题都交给RMI去一一处理。
2. Skeleton(一些书翻译叫骨架,也叫结构体):它的内部就是真正封装了一个类的形成调用体现机制。包括我们熟知的ServerSocket创建、接受、监听、处理等。
3. Mashalling(编组):在内存中的对象转换成字节流,以便能够通过网络连接传输。
4. Unmashalling(反编组):在内存中把字节流转换成对象,以便本地化调用。
5. Serialization(序列化):编组中使用到的技术叫序列化。
6. Deserializationg(反序列化):反编组中使用到的技术叫反序列化。
客户端
既然我们知道stub主要是以接口的方式来暴露体现,而stub主要 也是以代理的方式来具体实施。那在RMI中的这种接口有哪些特性呢?(Remote Interface)
1) 必须扩展(extends)java.rmi.Remote接口,因为远程接口并不包含任何一个方法,而是作为一个标记出现,它就是需要告诉JVM在RunTime的时候哪些是常规对象,哪些属于远程对象。通过这种标识的定义能让JVM了解类中哪些方法需要编组,通过了编组的方式才能通过网络序列化的调用;
2) 接口必须为public(公共),它的好处不言而喻的——能够方便的让所有人员来调用。
3) 接口方法还需要以异常抛出(例如:RemoteException),至于它的用处我们在前面也提到这里就不再复述;
4) 在调用一个远程对象期间(运行期间),方法的参数和返回值都要必须是可序列化的。至于为什么需要这么做?这里的缘由不用多说大家也应该清楚了解。
服务端
既然我们知道stub所做的事情是一个简单的代理转发动作,那我们真正要做的对象就在服务端来做了。对于使用简单的RMI我们直接去指定,但是往往一旦使用了RMI对象就存在非常多的远程方法调用,这个时候服务器端对于这么多的调用如何来判别或者说识别呢?这里就要说到的是对于RMI实现它会创建一个标识符,以便以后的stub可以调用转发给服务器对象使用了,而这种方式我们通常叫服务器RMI的注册机制。言外之意就是让服务器端的对象注册在RMI机制中,然后可以导出让今后的stub按需来调用。那它又是如何做到这种方式的呢?对于RMI来说有两种方式可以达到这种效果:
a) 直接使用UnicastRemoteObject的静态方法:exportObject;
b) 继承UnicastRemoteObject类则缺省的构造函数exportObject。
现在大家又会问他们之间又有什么区别呢?我该使用哪种方式来做呢,这不是很难做抉择吗?从一般应用场景来说区别并不是很大,但是,这里说了“但是”哦,呵呵。大家知道继承的方式是把父类所具备的所有特质都可以完好无损的继承到子类中而对于类的总老大:Object来说里面有:equals()、hashCode()、toString()等方法。这是个什么概念呢?意思就是说如果对于本地化的调用,他们两个的方法(a,b)基本区别不是很大。但是我们这里强调的RMI如果是一种分布式的特定场景,具备使用哈希表这种特性就显得尤为重要了。
刚才说了服务端采用什么方法行为导出对象的。那现在导出后的对象又对应会发生什么情况呢?
首先被导出的对象被分配一个标识符,这个标识符被保存为:java.rmi.server.ObjID对象中并被放到一个对象列表中或者一个映射中。而这里的ID是一个关键字,而远程对象则是它的一个值(说到这大家有没有觉得它原理非常像HashMap的特质呢?没错,其实就是使用了它的特性),这样它就可以很好的和前面创建的stub沟通。如果调用了静态方法UnicastRemoteObject.export(Remote …),RMI就会选择任意一个端口号,但这只是第一调用发生在随后的exportObject每次调用都把远程对象导出到该远程对象第一被导出时使用的端口号。这样就不会产生混乱,会把先前一一导出的对象全部放入到列表中。当然如果采用的是指定端口的,则按照对应显示的调用方式使用。这里稍作强调的是一个端口可以导出任意数目的对象。
(待续……)
posted on 2009-02-02 12:04
叶澍成 阅读(3379)
评论(3) 编辑 收藏 所属分类:
java基础 、
分布式