一辈子的程序员?

爱你一生不变-芳芳!
posts - 27, comments - 15, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2006年10月11日

    何去何从?有点迷茫,是沉寂在温柔之乡还是去迎接狂风暴雨!
看来自己还是没有自信!bs..........myself

posted @ 2007-08-15 14:32 boddi 阅读(574) | 评论 (3)编辑 收藏

2007最新骗局:

     建行一同志转送: 今天经过一栋大楼门口,门口有一提款机。有一个老伯,一直 

看著我走过他身边,突然叫住我。他说他不识字,拿一张提款卡要我帮他在大楼门口 

的自动提款机取钱。我回答我无法帮你取,叫警卫帮你。结果,他就回答我说不用了 

,继续找其他路人帮他取钱。朋友们要记住---取款机可是有摄影机耶。万一他说我抢 

劫或是偷他的提款卡,甚至他的卡片是偷来的,帮他领钱会在提款机留下影像,绝对 

会让你百口莫辩!我会警惕 ! 是因为已有同事上当,目前仍官司缠身。显然这是诈骗 

集团在找替身了! 请用力传出去~~~ 骗案真是层出不穷,一不小心就会踏入陷阱,真是 

令人防不胜防!

提醒各位朋友在外多小心! 

(2)一业主,家中突然断电,看到窗户外别人家里都有电,就出门查看自家电 

表箱,打开门就被刀子顶着了——持刀入室抢劫伤人家里突然断电,不要贸然就开门 

查看,有猫眼的多观察一会门外动静,没猫眼的也隔着门静听一段时间,没有异常响 

动再开门。 

(3)各位女同胞们注意了!这是最新骗局 

女同胞请注意 男同胞请叫自己的朋友注意 新出的情况,女性朋友要特别注意啦: 

一位上班的小姐在下班回家的路上看到一个小孩子一直哭,很可怜 ,然后就过去问那 

小朋友怎么了.小朋友就跟那个小姐说: 我迷路了,可以请你带我回家吗?然后拿一张 

纸条给她看, 说那是他家地址.然后她就笨笨的带小孩子去了.一般人都有同情心,然后 

带到那个所谓小孩子的家里以后,她一按铃,门铃像是有高压电,就失去知觉了.隔天醒 

来就被脱光光在一间空屋里,身边什么都没有了,她甚至连犯人长啥样子都没看见.所以 

,现在人犯案都是利用同情心啊,如果遇到类似这种的,千万别带他去,要带就带他到派 

出所去好了,走丢的小孩放到派出所一定没错啦,请通知身边所有女性,为了广大女士 

的安全,看完后麻烦给转发给所有人。 

(4)今天遇到讨饭新招,大家注意提防了~~ 

今天在家休息,有人按门铃,开门一看,是个50来岁的老妇女,手里拿了2包喜糖,我 

还以为是邻居来分喜糖的,结果一开口,听得出不是本地人,她说什么这2包糖给我们 

的,图个喜气,要换一点钱给她,后面还说了一大堆不知道什么,我也没听清楚,感 

觉就是不对,吓的马上关门,晕!这年头,还有这么讨钱的一刚。 

(5)转发:大家注意了!到自动取款机取钱时一定要倍加小心!!!!! 

昨晚在工行自动取款机取钱时,后面来了个老妇女,问我能不能取钱,还说 

什么取款机有个键可能坏了,旁边不知什么时候来了个小女孩,一直想我身边跻,我 

也没在意,小孩子淘气嘛,可是过分的是她竟然把手朝出钞口放,准备拿我的钱了, 

我感觉不对劲了,立即把她推到一边,等着把钱取出来。之后我想了一下,她们俩给 

我设了个套:老妇女负责和我瞎聊,吸引我的注意力,小女孩趁我不注意时抢走我的 

钱!如果我不防备的话,钱说不定就被抢走了,这样的话,我就进套了:(一则我立 

即去追小女孩,去追回我的钱,可是谁又会相信一个小女孩能抢我一个大人的钱呢? 

更可怕的是站在我后面的老妇女将会取光我卡中所有的钱,因为我的卡还在取款机里 

面;二则我不立即去追小女孩,等拿到卡再追,到那时小女孩就无影无踪了,钱也就 

没了啊:(她们真的很聪明,很可耻的!!!) 

这是我的亲身经历,希望大家以后取钱时一定要警惕起来,注意观察周围的所有人, 

并转告周围的家人、同事、朋友,让坏蛋分子没有可乘之机!!!!又出现骗局新招 

!! 

(6)我父母都退休在家。昨天上午,来一陌生中年人,说自己摩托车油开没了,加油 

站太远,摩托车又太重推不动,所以想问我父母要一个可乐瓶去买汽油,刚开口就说 

实在不行就出2、3元买一个空瓶好了。我母亲就拿了个空瓶给他,别说他还真从口袋 

里掏出钱来,不过是几张百元大钞,还让我父母找钱。我母亲顿生警觉,说算了,不 

过是一个空瓶而已。他10元钱买下来,只不过还是那张百元大钞。好在我母 

亲尚未龙钟,也不是那种爱贪小便宜的人 

女性朋友一定要认真看完,注意自我安全啊,现在万恶的社会。。。。朋友发给我一 

篇报道,现转给各位看看 ,出门在外,千万小心,小心千万。。。 

(7)一对新婚夫妇到巴黎度蜜月。在巴黎,妻子在一间时尚服装店试衣服 ,身为丈夫 

就在试衣间外等候。但等候多时却不见妻子走出来 ,紧张的丈夫要求店员帮忙到里头 

查看 ,却意外发现试衣间空无一人。丈夫以为妻子开玩笑作弄人 ,要他紧张.于是回到 

酒店等她回来。几小时后却不见妻子的踪影,才知事态严重。丈夫赶忙报警 ,并到巴黎 

所有服装店和医院询问妻子下落。三星期过去了,妻子犹如从人间蒸发,音讯全无,伤 

心的丈夫只能收拾包袱回到 ** 。由于无法从绝望中振作,丈夫无心工作,甚至独自生 

活 ,决定把自己放逐,流浪到各地方。几年后 ,他心血来潮到巴厘岛,在一破旧的屋子 

参观一畸形秀 ( freak show ) 。他见到一脏生锈的铁笼里,有一女人四肢全无,身躯, 

包括脸部,犹如破布般残破 ,充满疤痕。她在地上扭曲着 ,并发出有如野兽般的呻吟声 

。突然间男人惊恐地发出尖叫声。他从那毫无人样的女人脸上见到,他再熟悉不过,属 

于他新婚不久就告失踪的妻子脸上的红色胎记。 

(8)另一版本则发生在上海。几年前一女通知公安她的表妹在上海市集购物时无故失 

踪,可是遍寻不着 ,直到五年后一友人撞见这表妹在泰国曼谷街道上行乞。恐怖的是她 

不知何故没了双手双脚,身子被铁链绑在灯柱旁。 

(9)这是在某一对夫妇去香港游玩时发生的故事。一对夫妻不知不觉走入了全香港治 

安最坏的地区的一家精品店里 ? 妻子对店里的衣服样式十分喜欢 ,随后就进入试衣间 

试衣。可是,先生在外头等了又等,却不见妻子出来。由于实在是等太久了 ,所以先生 

开门 进去找她,可是试衣间里早已空空如也。他吃惊地向店员询问妻子到哪里去了,可 

是店员们却好象是串通好了一样,都说没有看见,并坚持根本没有象他妻子这样的人来 

过店里。因此他只好请当地的警察协助搜索这家精品店 ,可是却一无所获。后来他又 

一个人找了一段时间 ,直到他的签证到期。最后不得已他就在找不到妻子的情况下回 

国了。之后经过了一年 … 他向公司请了一段长假,再一次回到香港去找他的妻子。他 

带着妻子的相片走遍香港的大街小巷,但这次仍是一点线索也没有。终于假期就要结束 

了,他身心疲惫地开始考虑要回国的时候 ,有一天无意间经过了一间珍奇小屋。小屋的 

看板上写着 : 达磨(不倒翁) 虽然他对珍奇事物并不感兴趣 ,但由于连日疲劳他想 

让自己改变一下心情 , 加上看板上写着 达磨的文字也引起他的兴趣。最后他决定 

进去瞧瞧。但是他不该进去的!因为珍奇小屋里面展出一件令他惨不忍睹的东 … 小屋 

里的舞台上有一位手脚都被切断的全裸女性被当成花瓶一样摆在那里 ! 这位女性的舌 

头已经被拔掉了,不断发出奇怪的呻吟声。看到这么恶心的东西真令他恨不得马上拔腿 

就跑 ,但不知为什么他心里感受到一股奇怪的气氛 ,于是他又重新仔细看那女人的面 

孔 … 没错!这女人正是他一年前失踪的妻子。后来,他向当地的黑道支付庞大的赎金 

换取妻子的剩下的躯干。但一切都太迟了,他可怜的妻子早就已经疯了。现在她还住在 

国内某家医院,继续不断地发出奇怪的呻吟声 … 

(10)最近有人告诉我,他的朋友在晚上听到门口有婴儿在哭,不过当时已很晚了, 

而且她认为这件事很奇怪,于是她打电话给警察。警察告诉她∶ 「无论如何,绝对不 

要开 门。」这位女士表示那声音听起来象是婴儿爬到窗户附近哭,她担心婴儿会爬到 

街上,被车子碾过。警察告诉她∶我们已派人前往,无论如何不能开门。警方认为这 

是一个连续杀人犯,利用婴儿哭声的录音带,诱使女性以为有人在外面遗弃婴儿,她 

们出门察看。虽然尚未证实此事,但是警方已接到许多女性打电话来说,他们晚上独 

自在家时,听到门外有婴儿的哭声,请将这个消息传给其他人,不要因为听到婴儿的 

哭声而开门。 

请严肃看待这贴子!有这么离谱!小心为妙!!! 

以前听说大活人现场失踪,后来被卖到马戏团、被卖器官什么的,只当是天方夜谭。 

结果真的看到发生在真实中的恐怖故事。 

(11)事情是同事群发邮件告知的。她的朋友,简称小a吧,上周和两个女孩子,简称 

小b和小c,去逛罗湖商业城。罗湖商业城是深圳假货集散地,龙蛇混杂,紧靠深圳火 

车站和香港的罗湖口岸,人流量非常大。话说小c内急,就去上卫生间,小a和小b在洗 

手间外面等。等了很久很久,还是不见小c出来,两个人有点奇怪了。于是两个人进去 

催她。谁知道进去一看,人影全无。两个人倒竖一口冷气,打手机也没人接。一个大 

活人,难道就这么活不见人死不见尸的失踪了?于是赶紧报警。 警察来了,问情事情 

经过以后,说了一句令人无比毛骨悚然的话,“你们有没有看见其他可疑的人进去? 

”,两个人再三回忆,没有。因为不可能带着一个活生生的100多斤的人出来,而她们 

不注意。这时候小a突然想起来,其间有个清洁工打扮的人推着一辆清洁小车进去、接 

着又出来…… 警察告诉他们,这种事情已经不是第一次发生,现在深圳警方初步怀疑 

一个犯罪团伙,有组织地在管理疏松的低档商业城,利用人们,尤其是女性对清洁工 

没有防范意识的心理,进行有组织地绑架、贩卖人体器官犯罪。别忘了,罗湖商业城 

离香港和深圳火车站有多么地近。现在已经几天过去了,那个可怜的小c姑娘,仍是活 

不见人死不见尸。我的同事说,小a,也就是我同事的朋友,仍在等小c的消息。但是 

很可能,也许如果幸运的话,活着的小c会被扔在哪个角落,只是失去了她的肾,但是 

,更有可能的是,也许再过几天或者几个月、几年,小c的头颅和躯体、四肢会在深圳 

的城乡结合部的垃圾堆被人发现。如果看到这个发生在身边的活生生的恐怖事件,请 

转告身边的女性亲友,一定小心防范清洁工打扮的人,因为他/她很可能会趁你不注意 

把你敲晕,放进清洁车拉走,接下来等着你的是无比恐怖的活人分尸。 

posted @ 2007-03-05 17:11 boddi 阅读(391) | 评论 (2)编辑 收藏

远程方法调用入门指南(Java RMI Tutorial)


Java R MI Tutorial

远程方法调用入门指南

Copyright © 2005 Stephen Suen. All rights reserved.

Java 远程方法调用(Remote Method Invocation, RMI)使得运行在一个 Java 虚拟机(Java Virtual Machine, JVM)的对象可以调用运行另一个 JVM 之上的其他对象的方法,从而提供了程序间进行远程通讯的途径。RMI 是 J2EE 的很多分布式技术的基础,比如 RMI-IIOP 乃至 EJB。本文是 RMI 的一个入门指南,目的在于帮助读者快速建立对 Java RMI 的一个感性认识,以便进行更深层次的学习。事实上,如果你了解 RMI 的目的在于更好的理解和学习 EJB,那么本文就再合适不过了。通过本文所了解的 RMI 的知识和技巧,应该足够服务于这个目的了。

本文的最新版本将发布在程序员咖啡馆网站上(建设中)。欢迎订阅我们的邮件组,以获得关于本文的正式发布及更新信息。

全文在保证完整性,且保留全部版权声明(包括上述链接)的前提下可以在任意媒体转载——须保留此标注。


1. 简介

我们知道远程过程调用(Remote Procedure Call, RPC)可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式 对象间的通讯,允许我们获得在远程进程中的对象(称为远程对象)的引用(称为远程引用),进而通过引用调用远程对象的方法,就好像该对象是与你的客户端代码同样运行在本地进程中一样。RMI 使用了术语"方法"(Method)强调了这种进步,即在分布式基础上,充分支持面向对象的特性。

RMI 并不是 Java 中支持远程方法调用的唯一选择。在 RMI 基础上发展而来的 RMI-IIOP(Java Remote Method Invocation over the Internet Inter-ORB Protocol),不但继承了 RMI 的大部分优点,并且可以兼容于 CORBA。J2EE 和 EJB 都要求使用 RMI-IIOP 而不是 RMI。尽管如此,理解 RMI 将大大有助于 RMI-IIOP 的理解。所以,即便你的兴趣在 RMI-IIOP 或者 EJB,相信本文也会对你很有帮助。另外,如果你现在就对 API 感兴趣,那么可以告诉你,RMI 使用 java.rmi 包,而 RMI-IIOP 则既使用 java.rmi 也使用扩展的 javax.rmi 包。

本文的随后内容将仅针对 Java RMI。

2. 分布式对象

在学习 RMI 之前,我们需要了解一些基础知识。首先需要了解所谓的分布式对象(Distributed Object)。分布式对象是指一个对象可以被远程系统所调用。对于 Java 而言,即对象不仅可以被同一虚拟机中的其他客户程序(Client)调用,也可以被运行于其他虚拟机中的客户程序调用,甚至可以通过网络被其他远程主机之上的客户程序调用。

下面的图示说明了客户程序是如何调用分布式对象的:

从图上我们可以看到,分布式对象被调用的过程是这样的:

  1. 客户程序调用一个被称为 Stub (有时译作存根,为了不产生歧义,本文将使用其英文形式)的客户端代理对象。该代理对象负责对客户端隐藏网络通讯的细节。Stub 知道如何通过网络套接字(Socket)发送调用,包括如何将调用参数转换为适当的形式以便传输等。

  2. Stub 通过网络将调用传递到服务器端,也就是分布对象一端的一个被称为 Skeleton 的代理对象。同样,该代理对象负责对分布式对象隐藏网络通讯的细节。Skeleton 知道如何从网络套接字(Socket)中接受调用,包括如何将调用参数从网络传输形式转换为 Java 形式等。

  3. Skeleton 将调用传递给分布式对象。分布式对象执行相应的调用,之后将返回值传递给 Skeleton,进而传递到 Stub,最终返回给客户程序。

这个场景基于一个基本的法则,即行为的定义和行为的具体实现相分离。如图所示,客户端代理对象 Stub 和分布式对象都实现了相同的接口,该接口称为远程接口(Remote Interface)。正是该接口定义了行为,而分布式对象本身则提供具体的实现。对于 Java RMI 而言,我们用接口(interface)定义行为,用类(class)定义实现。

3. RMI 架构

RMI 的底层架构由三层构成:

  • 首先是 Stub/Skeleton 层。该层提供了客户程序和服务程序彼此交互的接口。

  • 然后是远程引用(Remote Reference)层。这一层相当于在其之上的 Stub/Skeleton 层和在其之下的传输协议层之前的中间件,负责处理远程对象引用的创建和管理。

  • 最后是传输协议(Transport Protocol) 层。该层提供了数据协议,用以通过线路传输客户程序和远程对象间的请求和应答。

这些层之间的交互可以参照下面的示意图:

和其它分布式对象机制一样,Java RMI 的客户程序使用客户端的 Stub 向远程对象请求方法调用;服务器对象则通过服务器端的 Skeleton 接受请求。我们深入进去,来看看其中的一些细节。

注意: 事实上,在 Java 1.2 之后,RMI 不再需要 Skeleton 对象,而是通过 Java 的反射机制(Reflection)来完成对服务器端的远程对象的调用。为了便于说明问题,本文以下内容仍然基于 Skeleton 来讲解。

当客户程序调用 Stub 时,Stub 负责将方法的参数转换为序列化(Serialized)形式,我们使用一个特殊的术语,即编列(Marshal)来指代这个过程。编列的目的是将这些参数转换为可移植的形式,从而可以通过网络传输到远程的服务对象一端。不幸的是,这个过程没有想象中那么简单。这里我们首先要理解一个经典的问题,即方法调用时,参数究竟是传值还是传引用呢?对于 Java RMI 来说,存在四种情况,我们将分别加以说明。

  • 对于基本的原始类型(整型,字符型等等),将被自动的序列化,以传值的方式编列。

  • 对于 Java 的对象,如果该对象是可序列化的(实现了 java.io.Serializable 接口),则通过 Java 序列化机制自动地加以序列化,以传值的方式编列。对象之中包含的原始类型以及所有被该对象引用,且没有声明为 transient 的对象也将自动的序列化。当然,这些被引用的对象也必须是可序列化的。

  • 绝大多数内建的 Java 对象都是可序列化的。 对于不可序列化的 Java 对象(java.io.File 最典型),或者对象中包含对不可序列化,且没有声明为 transient 的其它对象的引用。则编列过程将向客户程序抛出异常,而宣告失败。

  • 客户程序可以调用远程对象,没有理由禁止调用参数本身也是远程对象(实现了 java.rmi.Remote 接口的类的实例)。此时,RMI 采用一种模拟的传引用方式(当然不是传统意义的传引用,因为本地对内存的引用到了远程变得毫无意义),而不是将参数直接编列复制到远程。这种情况下,交互的双方发生的戏剧性变化值得我们注意。参数是远程对象,意味着该参数对象可以远程调用。当客户程序指定远程对象作为参数调用服务器端远程对象的方法时,RMI 的运行时机制将向服务器端的远程对象发送作为参数的远程对象的一个 Stub 对象。这样服务器端的远程对象就可以回调(Callback)这个 Stub 对象的方法,进而调用在客户端的远程对象的对应方法。通过这种方法,服务器端的远程对象就可以修改作为参数的客户端远程对象的内部状态,这正是传统意义的传引用所具备的特性。是不是有点晕?这里的关键是要明白,在分布式环境中,所谓服务器和客户端都是相对的。被请求的一方就是服务器,而发出请求的一方就是客户端。

在调用参数的编列过程成功后,客户端的远程引用层从 Stub 那里获得了编列后的参数以及对服务器端远程对象的远程引用(参见 java.rmi.server.RemoteRef API)。该层负责将客户程序的请求依据底层的 RMI 数据传输协议转换为传输层请求。在 RMI 中,有多种的可能的传输机制,比如点对点(Point-to-Point)以及广播(Multicast)等。不过,在当前的 JMI 版本中只支持点对点协议,即远程引用层将生成唯一的传输层请求,发往指定的唯一远程对象(参见 java.rmi.server.UnicastRemoteObject API)。

在服务器端,服务器端的远程引用层接收传输层请求,并将其转换为对远程对象的服务器端代理对象 Skeleton 的调用。Skeleton 对象负责将请求转换为对实际的远程对象的方法调用。这是通过与编列过程相对的反编列(Unmarshal)过程实现的。所有序列化的参数被转换为 Java 形式,其中作为参数的远程对象(实际上发送的是远程引用)被转换为服务器端本地的 Stub 对象。

如果方法调用有返回值或者抛出异常,则 Skeleton 负责编列返回值或者异常,通过服务器端的远程引用层,经传输层传递给客户端;相应地,客户端的远程引用层和 Stub 负责反编列并最终将结果返回给客户程序。

整个过程中,可能最让人迷惑的是远程引用层。这里只要明白,本地的 Stub 对象是如何产生的,就不难理解远程引用的意义所在了。远程引用中包含了其所指向的远程对象的信息,该远程引用将用于构造作为本地代理对象的 Stub 对象。构造后,Stub 对象内部将维护该远程引用。真正在网络上传输的实际上就是这个远程引用,而不是 Stub 对象。

4. RMI 对象服务

在 RMI 的基本架构之上,RMI 提供服务与分布式应用程序的一些对象服务,包括对象的命名/注册(Naming/Registry)服务,远程对象激活(Activation)服务以及分布式垃圾收集(Distributed Garbage Collection, DGC)。作为入门指南,本文将指介绍其中的命名/注册服务,因为它是实战 RMI 所必备的。其它内容请读者自行参考其它更加深入的资料。

在前一节中,如果你喜欢刨根问底,可能已经注意到,客户端要调用远程对象,是通过其代理对象 Stub 完成的,那么 Stub 最早是从哪里得来的呢?RMI 的命名/注册服务正是解决这一问题的。当服务器端想向客户端提供基于 RMI 的服务时,它需要将一个或多个远程对象注册到本地的 RMI 注册表中(参见java.rmi.registry.Registry API)。每个对象在注册时都被指定一个将来用于客户程序引用该对象的名称。客户程序通过命名服务(参见 java.rmi.Naming API),指定类似 URL 的对象名称就可以获得指向远程对象的远程引用。在 Naming 中的 lookup() 方法找到远程对象所在的主机后,它将检索该主机上的 RMI 注册表,并请求所需的远程对象。如果注册表发现被请求的远程对象,它将生成一个对该远程对象的远程引用,并将其返回给客户端,客户端则基于远程引用生成相应的 Stub 对象,并将引用传递给调用者。之后,双方就可以按照我们前面讲过的方式进行交互了。

注意: RMI 命名服务提供的 Naming 类并不是你的唯一选择。RMI 的注册表可以与其他命名服务绑定,比如 JNDI,这样你就可以通过 JNDI 来访问 RMI 的注册表了。

5. 实战 RMI

理论离不开实践,理解 RMI 的最好办法就是通过例子。开发 RMI 的分布式对象的大体过程包括如下几步:

  1. 定义远程接口。这一步是通过扩展 java.rmi.Remote 接口,并定义所需的业务方法实现的。

  2. 定义远程接口的实现类。即实现上一步所定义的接口,给出业务方法的具体实现逻辑。

  3. 编译远程接口和实现类,并通过 RMI 编译器 rmic 基于实现类生成所需的 Stub 和 Skeleton 类。

RMI 中各个组件之间的关系如下面这个示意图所示:

回忆我们上一节所讲的,Stub 和 Skeleton 负责代理客户和服务器之间的通讯。但我们并不需要自己生成它们,相反,RMI 的编译器 rmic 可以帮我们基于远程接口和实现类生成这些类。当客户端对象通过命名服务向服务器端的 RMI 注册表请求远程对象时,RMI 将自动构造对应远程对象的 Skeleton 实例对象,并通过 Skeleton 对象将远程引用返回给客户端。在客户端,该远程引用将用于构造 Stub 类的实例对象。之后,Stub 对象和 Skeleton 对象就可以代理客户对象和远程对象之间的交互了。

我们的例子展现了一个简单的应用场景。服务器端部署了一个计算引擎,负责接受来自客户端的计算任务,在服务器端执行计算任务,并将结果返回给客户端。客户端将发送并调用计算引擎的计算任务实际上是计算指定精度的 π 值。

重要: 本文的例子改编自 The Java™ Tutorial Trail:RMI。所有权利属于相应的所有人。

6. 定义远程接口

定义远程接口与非分布式应用中定义接口的方法没有太多的区别。只要遵守下面两个要求:

  • 远程接口必须直接或者间接的扩展自 java.rmi.Remote 接口。远程接口还可以在扩展该接口的基础上,同时扩展其它接口,只要被扩展的接口的所有方法与远程接口的所有方法一样满足下一个要求。

  • 在远程接口或者其超接口(Super-interface)中声明的方法必须满足下列对远程方法的要求:

    • 远程方法必须声明抛出 java.rmi.RemoteException 异常,或者该异常的超类(Superclass),比如 java.io.IOException 或者 java.lang.Exception 异常。在此基础上,远程方法可以声明抛出应用特定的其它异常。

    • 在远程方法声明中,作为参数或者返回值的远程对象,或者包含在其它非远程对象中的远程对象,必须声明为其对应的远程接口,而不是实际的实现类。

注意: 在 Java 1.2 之前,上面关于抛出异常的要求更严格,即必须抛出 java.rmi.RemoteExcption,不允许类似 java.io.IOException 这样的超类。现在之所以放宽了这一要求,是希望可以使定义既可以用于远程对象,也可以用于本地对象的接口变得容易一些(想想 EJB 中的本地接口和远程接口)。当然,这并没有使问题好多少,你还是必须声明异常。不过,一种观点认为这不是问题,强制声明异常可以使开发人员保持清醒的头脑,因为远程对象和本地对象在调用时传参的语意是不同的。本地对象是传引用,而远程对象主要是传值,这意味对参数内部状态的修改产生的结果是不同的。

对于第一个要求,java.rmi.Remote 接口实际上没有任何方法,而只是用作标记接口。RMI 的运行环境依赖该接口判断对象是否是远程对象。第二个要求则是因为分布式应用可能发生任何问题,比如网络问题等等。

例 1 列出了我们的远程接口定义。该接口只有一个方法:executeTask() 用以执行指定的计算任务,并返回相应的结果。注意,我们用后缀 Remote 表明接口是远程接口。

例 1. ComputeEngineRemote 远程接口

package rmitutorial;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ComputeEngineRemote extends Remote {
    public Object executeTask(Task task) throws RemoteException;    
}

例 2 列出了计算任务接口的定义。该接口也只有一个方法:execute() 用以执行实际的计算逻辑,并返回结果。注意,该接口不是远程接口,所以没有扩展 java.rmi.Remote 接口;其方法也不必抛出 java.rmi.RemoteException 异常。但是,因为它将用作远程方法的参数,所以扩展了 java.io.Serializable 接口。

例 2. Task 接口

package rmitutorial;

import java.io.Serializable;

public interface Task extends Serializable {
    Object execute();
}

7. 实现远程接口

接下来,我们将实现前面定义的远程接口。例 3给出了实现的源代码。

例 3. ComputeEngine 实现

package rmitutorial;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class ComputeEngine extends UnicastRemoteObject 
        implements ComputeEngineRemote {
    
    public ComputeEngine() throws RemoteException {
        super();
    }
    
    public Object executeTask(Task task) throws RemoteException {
        return task.execute();
    }
}

ComputeEngine 实现了之前定义的远程接口,同时继承自 java.rmi.server.UnicastRemoteObject 超类。UnicastRemoteObject 类是一个便捷类,它实现了我们前面所讲的基于 TCP/IP 的点对点通讯机制。远程对象都必须从该类扩展(除非你想自己实现几乎所有 UnicastRemoteObject 的方法)。在我们的实现类的构造函数中,调用了超类的构造函数(当然,即使你不显式的调用这个构建函数,它也一样会被调用。这里这样做,只是为了突出强调这种调用而已)。该构造函数的最重要的意义就是调用 UnicastRemoteObject 类的 exportObject() 方法。导出(Export)对象是指使远程对象准备就绪,可以接受进来的调用的过程。而这个过程的最重要内容就是建立服务器套接字,监听特定的端口,等待客户端的调用请求。

8. 引导程序

为了让客户程序可以找到我们的远程对象,就需要将我们的远程对象注册到 RMI 的注册表。这个过程有时被称为"引导"过程(Bootstrap)。我们将为此编写一个独立的引导程序负责创建和注册远程对象。例 4 给出了引导程序的源代码。

例 4. 引导程序

package rmitutorial;

import java.rmi.Naming;
import java.rmi.RMISecurityManager;

public class Bootstrap {
    
    public static void main(String[] args) throws Exception {
        String name = "ComputeEngine";
        
        ComputeEngine engine = new ComputeEngine();
        System.out.println("ComputerEngine exported");
        
        Naming.rebind(name, engine);
        System.out.println("ComputeEngine bound");
    }
}

可以看到,我们首先创建了一个远程对象(同时导出了该对象),之后将该对象绑定到 RMI 注册表中。Namingrebind() 方法接受一个 URL 形式的名字作绑定之用。其完整格式如下:

protocol://host:port/object

其中,协议(Protocol)默认为 rmi;主机名默认为 localhost;端口默认为 1099。注意,JDK 中提供的默认 Naming 实现只支持 rmi 协议。在我们的引导程序里面只给出了对象绑定的名字,而其它部分均使用缺省值。

9. 客户端程序

例 5 给出了我们的客户端程序。该程序接受两个参数,分别是远程对象所在的主机地址和希望获得的 π 值的精度。

例 5. Client.java

package rmitutorial;

import java.math.BigDecimal;
import java.rmi.Naming;

public class Client {
    public static void main(String args[]) throws Exception {
            String name = "rmi://" + args[0] + "/ComputeEngine";
            ComputeEngineRemote engineRemote = 
                    (ComputeEngineRemote)Naming.lookup(name);
            Pi task = new Pi(Integer.parseInt(args[1]));
            BigDecimal pi = (BigDecimal)(engineRemote.executeTask(task));
            System.out.println(pi);
    }
}

例 6. Pi.java

package rmitutorial;

import java.math.*;

public class Pi implements Task {
    
    private static final BigDecimal ZERO =
            BigDecimal.valueOf(0);
    private static final BigDecimal  ONE =
            BigDecimal.valueOf(1);
    private static final BigDecimal FOUR =
            BigDecimal.valueOf(4);
    
    private static final int roundingMode =
            BigDecimal.ROUND_HALF_EVEN;
    
    private int digits;
    
    public Pi(int digits) {
        this.digits = digits;
    }
    
    public Object execute() {
        return computePi(digits);
    }
    
    public static BigDecimal computePi(int digits) {
        int scale = digits + 5;
        BigDecimal arctan1_5 = arctan(5, scale);
        BigDecimal arctan1_239 = arctan(239, scale);
        BigDecimal pi = arctan1_5.multiply(FOUR).subtract(
                arctan1_239).multiply(FOUR);
        return pi.setScale(digits,
                BigDecimal.ROUND_HALF_UP);
    }

    public static BigDecimal arctan(int inverseX,
            int scale) {
        BigDecimal result, numer, term;
        BigDecimal invX = BigDecimal.valueOf(inverseX);
        BigDecimal invX2 =
                BigDecimal.valueOf(inverseX * inverseX);
        
        numer = ONE.divide(invX, scale, roundingMode);
        
        result = numer;
        int i = 1;
        do {
            numer =
                    numer.divide(invX2, scale, roundingMode);
            int denom = 2 * i + 1;
            term =
                    numer.divide(BigDecimal.valueOf(denom),
                    scale, roundingMode);
            if ((i % 2) != 0) {
                result = result.subtract(term);
            } else {
                result = result.add(term);
            }
            i++;
        } while (term.compareTo(ZERO) != 0);
        return result;
    }
}

10. 编译示例程序

编译我们的示例程序和编译其它非分布式的应用没什么区别。只是编译之后,需要使用 RMI 编译器,即 rmic 生成所需 Stub 和 Skeleton 实现。使用 rmic 的方式是将我们的远程对象的实现类(不是远程接口)的全类名作为参数来运行 rmic 命令。参考下面的示例:

E:\classes\rmic rmitutorial.ComputeEngine

编译之后将生成 rmitutorial.ComputeEngine_Skelrmitutorial.ComputeEngine_Stub 两个类。

11. 运行示例程序

远程对象的引用通常是通过 RMI 的注册表服务以及 java.rmi.Naming 接口获得的。远程对象需要导出(注册)相应的远程引用到注册表服务,之后注册表服务就可以监听并服务于客户端对远程对象引用的请求。标准的 Sun Java SDK 提供了一个简单的 RMI 注册表服务程序,即 rmiregistry 用于监听特定的端口,等待远程对象的注册,以及客户端对这些远程对象引用的检索请求。

在运行我们的示例程序之前,首先要启动 RMI 的注册表服务。这个过程很简单,只要直接运行 rmiregistry 命令即可。缺省的情况下,该服务将监听 1099 端口。如果需要指定其它的监听端口,可以在命令行指定希望监听的端口(如果你指定了其它端口,需要修改示例程序以适应环境)。如果希望该程序在后台运行,在 Unix 上可以以如下方式运行(当然,可以缺省端口参数):

$ rmiregistry 1099 &

在 Windows 操作系统中可以这样运行:

C:\> start rmiregistry 1099

我们的 rmitutorial.Bootstrap 类将用于启动远程对象,并将其绑定在 RMI 注册表中。运行该类后,远程对象也将进入监听状态,等待来自客户端的方法调用请求。

$ java rmitutorial.Bootstrap
ComputeEngine exported
ComputeEngine bound

启动远程对象后,打开另一个命令行窗口,运行客户端。命令行的第一个参数为 RMI 注册表的地址,第二个参数为期望的 π 值精度。参考下面的示例:

$ java rmitutorial.Client localhost 50
3.14159265358979323846264338327950288419716939937511

12. 其它信息

在演示示例程序时,我们实际上是在同一主机上运行的服务器和客户端,并且无论是服务器和客户端所需的类都在相同的类路径上,可以同时被服务器和客户端所访问。这忽略了 Java RMI 的一个重要细节,即动态类装载。因为 RMI 的特性(包括其它几个特性)并不适用于 J2EE 的 RMI-IIOP 和 EJB 技术,所以,本文将不作详细介绍,请读者自行参考本文给出的参考资料。不过,为了让好奇的读者不至于过分失望,这里简单介绍一下动态类装载的基本思想。

RMI 运行时系统采用动态类装载机制来装载分布式应用所需的类。如果你可以直接访问应用所涉及的所有包括服务器端客户端在内的主机,并且可以把分布式应用所需的所有类都安装在每个主机的 CLASSPATH 中(上面的示例就是极端情况,所有的东西都在本地主机),那么你完全不必关心 RMI 类装载的细节。显然,既然是分布式应用,情况往往正相反。对于 RMI 应用,客户端需要装载客户端自身所需的类,将要调用的远程对象的远程接口类以及对应的 Stub 类;服务器端则要装载远程对象的实现类以及对应的 Skeleton 类(Java 1.2 之后不需要 Skeleton 类)。RMI 在处理远程调用涉及的远程引用,参数以及返回值时,可以将一个指定的 URL 编码到流中。交互的另一端可以通过 该 URL 获得处理这些对象所需的类文件。这一点类似于 Applet 中的 CODEBASE 的概念,交互的两端通过 HTTP 服务器发布各自控制的类,允许交互的另一端动态下载这些类。以我们的示例为例,客户端不必部署 ComputeEngine_Stub 的类文件,而可以通过服务器端的 HTTP 服务器获得类文件。同样,服务器端也不需要客户端实现的定制任务 Pi 的类文件。

注意,这种动态类装载将需要交互的两端加载定制的安全管理器(参见 java.rmi.RMISecurityManager API),以及对应的策略文件。

13. 参考资料

  • The Java™ Tutorial Trail:RMI

  • David Flanagan, Jim Farley, William Crawford and Kris Magnusson, 1999, ISBN 1-56592-483-5E, O'Reilly, Java™ Enterprise in a Nutshell

  • Ed Roman, Scott Ambler and Tyler Jewell 2002, ISBN 0-471-41711-4, John Wiley &Sons, Inc., Matering Enterprise JavaBeans™ , Second Edition

posted @ 2006-10-11 09:51 boddi 阅读(2113) | 评论 (1)编辑 收藏