74.JAVA编程思想——远程方法

74.JAVA编程思想——远程方法

为通过网络执行其他机器上的代码,传统的方法不仅难以学习和掌握,也极易出错。思考这个问题最佳的方式是:某些对象正好位于另一台机器,我们可向它们发送一条消息,并获得返回结果,就象那些对象位于自己的本地机器一样。Java 1.1 的“远程方法调用”(RMI)采用的正是这种抽象。将引导大家经历一些必要的步骤,创建自己的RMI 对象。

1     远程接口概念

RMI 对接口有着强烈的依赖。在需要创建一个远程对象的时候,我们通过传递一个接口来隐藏基层的实施细节。所以客户得到远程对象的一个句柄时,它们真正得到的是接口句柄。这个句柄正好同一些本地的根代码连接,由后者负责通过网络通信。但我们并不关心这些事情,只需通过自己的接口句柄发送消息即可。

创建一个远程接口时,必须遵守下列规则:

(1) 远程接口必须为public 属性(不能有“包访问”;也就是说,它不能是“友好的”)。否则,一旦客户试图装载一个实现了远程接口的远程对象,就会得到一个错误。

(2) 远程接口必须扩展接口java.rmi.Remote。

(3) 除与应用程序本身有关的违例之外,远程接口中的每个方法都必须在自己的throws 从句中声明java.rmi.RemoteException。

(4) 作为参数或返回值传递的一个远程对象(不管是直接的,还是在本地对象中嵌入)必须声明为远程接口,不可声明为实施类。

下面是一个简单的远程接口示例,它代表的是一个精确计时服务:

import java.rmi.*;

interface PerfectTimeI
extends Remote {

long getPerfectTime()
throws RemoteException;

}/// :~

它表面上与其他接口是类似的,只是对Remote 进行了扩展,而且它的所有方法都会“掷”出

RemoteException(远程违例)。记住接口和它所有的方法都是public 的。

2     远程接口的实施

服务器必须包含一个扩展了UnicastRemoteObject 的类,并实现远程接口。这个类也可以含有附加的方法,但客户只能使用远程接口中的方法。这是显然的,因为客户得到的只是指向接口的一个句柄,而非实现它的那个类。

必须为远程对象明确定义构建器,即使只准备定义一个默认构建器,用它调用基础类构建器。必须把它明确地编写出来,因为它必须“掷”出RemoteException 违例。

下面列出远程接口PerfectTime 的实施过程:

2.1     代码

import java.rmi.*;

import java.rmi.server.*;

import
java.rmi.registry.*;

import
java.net.*;

public
class
PerfectTime extends UnicastRemoteObjectimplementsPerfectTimeI {

//Implementation of the interface:

public
long
getPerfectTime() throws RemoteException {

return System.currentTimeMillis();

}

// Mustimplement constructor to throw

//RemoteException:

public PerfectTime()
throws RemoteException {

// super(); // Called automatically

}

//Registration for RMI serving:

public
staticvoid
main(String[]
args){

System.setSecurityManager(new
RMISecurityManager());

try {

PerfectTime
pt = new PerfectTime();

Naming.bind("//toad:2005/PerfectTime",
pt);

System.out.println("Ready to do time");

} catch (Exception
e) {

e.printStackTrace();

}

}

}/// :~

在这里,main()控制着设置服务器的全部细节。保存RMI 对象时,必须在程序的某个地方采取下述操作:

(1) 创建和安装一个安全管理器,令其支持RMI。作为Java 发行包的一部分,适用于RMI 唯一一个是RMISecurityManager。

(2) 创建远程对象的一个或多个实例。在这里,大家可看到创建的是PerfectTime 对象。

(3) 向RMI 远程对象注册表注册至少一个远程对象。一个远程对象拥有的方法可生成指向其他远程对象的句柄。这样一来,客户只需到注册表里访问一次,得到第一个远程对象即可。

1. 设置注册表

在这儿,大家可看到对静态方法Naming.bind()的一个调用。然而,这个调用要求注册表作为计算机上的一个独立进程运行。注册表服务器的名字是rmiregistry。在32 位Windows环境中,可使用:

start rmiregistry

令其在后台运行。在Unix 中,使用:

rmiregistry &

和许多网络程序一样,rmiregistry 位于机器启动它所在的某个IP 地址处,但它也必须监视一个端口。如果象上面那样调用rmiregistry,不使用参数,注册表的端口就会默认为1099。若希望它位于其他某个端口,只需在命令行添加一个参数,指定那个端口编号即可。对这个例子来说,端口将位于2005,所以rmiregistry 应该象下面这样启动(对于32 位Windows):

start rmiregistry 2005
(也是蛤蟆使用的)

对于Unix,则使用下述命令:

rmiregistry 2005 &

与端口有关的信息必须传送给bind()命令,同时传送的还有注册表所在的那台机器的IP 地址。但假若我们想在本地测试RMI 程序,就象本章的网络程序一直测试的那样,这样做就会带来问题。在JDK 1.1.1 版本中,存在着下述两方面的问题:

(1) localhost 不能随RMI 工作。所以为了在单独一台机器上完成对RMI 的测试,必须提供机器的名字。为了在32 位Windows 环境中调查自己机器的名字,可进入控制面板,选择“网络”,选择“标识”卡片,其中列出了计算机的名字。就我自己的情况来说,我的机器叫作“toad”。似乎大写形式会被忽略。

(2) 除非计算机有一个活动的TCP/IP连接,否则RMI 不能工作,即使所有组件都只需要在本地机器里互相通信。这意味着在试图运行程序之前,必须连接到自己的ISP(因特网服务提供者),否则会得到一些含义模糊的违例消息。

考虑到这些因素,bind()命令变成了下面这个样子:

Naming.bind("//toad:2005/PerfectTime",pt);

若使用默认端口1099,就没有必要指定一个端口,所以可以使用:

Naming.bind("//toad /PerfectTime",pt);

在JDK 未来的版本中(1.1之后),一旦改正了localhost 的问题,就能正常地进行本地测试,去掉IP 地址,只使用标识符:

Naming.bind("PerfectTime",pt);

服务名是任意的;它在这里正好为PerfectTime,和类名一样,但你可以根据情况任意修改。最重要的是确保它在注册表里是个独一无二的名字,以便客户正常地获取远程对象。若这个名字已在注册表里了,就会得到一个AlreadyBoundException 违例。为防止这个问题,可考虑坚持使用rebind(),放弃bind()。这是由于rebind()要么会添加一个新条目,要么将同名的条目替换掉。尽管main()退出,我们的对象已经创建并注册,所以会由注册表一直保持活动状态,等候客户到达并发出对它的请求。只要rmiregistry
处于运行状态,而且我们没有为名字调用Naming.unbind()方法,对象就肯定位于那个地方。考虑到这个原因,在我们设计自己的代码时,需要先关闭rmiregistry,并在编译远程对象的一个新版本时重新启动它。

并不一定要将rmiregistry 作为一个外部进程启动。若事前知道自己的是要求用以注册表的唯一一个应用,就可在程序内部启动它,使用下述代码:

LocateRegistry.createRegistry(2005);

和前面一样,2005 代表我们在这个例子里选用的端口号。这等价于在命令行执行rmiregistry 2005。但在设计RMI 代码时,这种做法往往显得更加方便,因为它取消了启动和中止注册表所需的额外步骤。一旦执行完这个代码,就可象以前一样使用Naming 进行“绑定”——bind()。

3     创建根与干

若编译和运行PerfectTime.java,即使rmiregistry 正确运行,它也无法工作。这是由于RMI 的框架尚未就位。首先必须创建根和干,以便提供网络连接操作,并使我们将远程对象伪装成自己机器内的某个本地对象。所有这些幕后的工作都是相当复杂的。我们从远程对象传入、传出的任何对象都必须“implement Serializable”(如果想传递远程引用,而非整个对象,对象的参数就可以“implement Remote”)。因此可以想象,当根和干通过网络“汇集”所有参数并返回结果的时候,会自动进行序列化以及数据的重新装配。幸运的是,我们根本没必要了解这些方面的任何细节,但根和干却是必须创建的。一个简单的过程如下:在编译好的代码中调用rmic,它会创建必需的一些文件。所以唯一要做的事情就是为编译过程新添一个步骤。

然而,rmic 工具与特定的包和类路径有很大的关联。PerfectTime.java 位于包c15.Ptime 中,即使我们调用与PerfectTime.class 同一目录内的rmic,rmic 都无法找到文件。这是由于它搜索的是类路径。因此,我们必须同时指定类路径,就象下面这样:

rmic c15.PTime.PerfectTime

执行这个命令时,并不一定非要在包含了PerfectTime.class 的目录中,但结果会置于当前目录。

若rmic 成功运行,目录里就会多出两个新类:

PerfectTime_Stub.class

PerfectTime_Skel.class

它们分别对应根(Stub)和干(Skeleton)。现在,我们已准备好让服务器与客户互相沟通了。

4     使用远程对象

RMI 全部的宗旨就是尽可能简化远程对象的使用。我们在客户程序中要做的唯一一件额外的事情就是查找并从服务器取回远程接口。自此以后,剩下的事情就是普通的Java 编程:将消息发给对象。下面是使用PerfectTime 的程序:

4.1     代码

import java.rmi.*;

import
java.rmi.registry.*;

public
class
DisplayPerfectTime {

public
staticvoid
main(String[]
args){

System.setSecurityManager(new
RMISecurityManager());

try {

PerfectTimeI
t = (PerfectTimeI)Naming.lookup("//127.0.0.1:2005/PerfectTime");

for (int
i = 0; i < 10;
i++)

System.out.println("Perfect time = "+
t.getPerfectTime());

} catch (Exception
e) {

e.printStackTrace();

}

}

} /// :~

ID 字串与那个用Naming 注册对象的那个字串是相同的,第一部分指出了URL 和端口号。由于我们准备使用一个URL,所以也可以指定因特网上的一台机器。

从Naming.lookup()返回的必须造型到远程接口,而不是到类。若换用类,会得到一个违例提示。

在下述方法调用中:

t.getPerfectTime( )

我们可看到一旦获得远程对象的句柄,用它进行的编程与用本地对象的编程是非常相似(仅有一个区别:远程方法会“掷”出一个RemoteException 违例)。

5     R M I 的替选方案

RMI 只是一种创建特殊对象的方式,它创建的对象可通过网络发布。它最大的优点就是提供了一种“纯Java”方案,但假如已经有许多用其他语言编写的代码,则RMI 可能无法满足我们的要求。目前,两种最具竞争力的替选方案是微软的DCOM(根据微软的计划,它最终会移植到除Windows 以外的其他平台)以及CORBA。CORBA 自Java1.1 便开始支持,是一种全新设计的概念,面向跨平台应用。在由Orfali 和Harkey编著的《Client/Server
Programming withJava and CORBA》一书中(John Wiley&Sons 1997 年出版),大家可获得对Java 中的分布式对象的全面介绍(该书似乎对CORBA 似乎有些偏见)。为CORBA 赋予一个较公正的对待的一本书是由Andreas Vogel 和Keith Duddy 编写的《Java Programming with CORBA》,JohnWiley&Sons 于1997 年出版。

6     总结

还有其他许多涉及连网的概念没。Java 也为URL 提供了相当全面的支持,包括为因特网上不同类型的客户提供协议控制器等等。除此以外,一种正在逐步流行的技术叫作Servlet Server。它是一种因特网服务器应用,通过Java 控制客户请求,而非使用以前那种速度很慢、且相当麻烦的CGI(通用网关接口)协议。这意味着为了在服务器那一端提供服务,我们可以用Java 编程,不必使用自己不熟悉的其他语言。由于Java 具有优秀的移植能力,所以不必关心具体容纳这个服务器是什么平台。

所有这些以及其他特性都在《Java Network Programming》一书中得到了详细讲述。该书由ElliotteRustyHarold 编著,O‘Reilly 于1997 年出版。

时间: 2024-10-08 15:25:38

74.JAVA编程思想——远程方法的相关文章

Java编程思想重点笔记(Java开发必看)

Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面 试过程中,而且在大型项目开发中也是常用的知识,既有简单的概念理解题(比如is-a关系和has-a关系的区别),也有深入的涉及RTTI和JVM底层 反编译知识. 1. Java中的多态性理解(注意与C++区分) Java中除了static方法和final方法(private方法本质上属于final方法,因为不能被子类访问)之外,其它所有的方法都是动态绑定,这意

66.JAVA编程思想——网络编程

66.JAVA编程思想--网络编程 历史上的网络编程都倾向于困难.复杂,而且极易出错. 程序员必须掌握与网络有关的大量细节,有时甚至要对硬件有深刻的认识.一般地,我们需要理解连网协议中不同的"层"(Layer).而且对于每个连网库,一般都包含了数量众多的函数,分别涉及信息块的连接.打包和拆包:这些块的来回运输:以及握手等等.这是一项令人痛苦的工作.但是,连网本身的概念并不是很难.我们想获得位于其他地方某台机器上的信息,并把它们移到这儿:或者相反.这与读写文件非常相似,只是文件存在于远程

java编程思想总结(三)

java编程思想总结(三) java编程思想总结是一个持续更新的系列,是本人对自己多年工作中使用到java的一个经验性总结,也是温故而知新吧,因为很多基础的东西过了这么多年,平时工作中用不到也会遗忘掉,所以看看书,上上网,查查资料,也算是记录下自己的笔记吧,过一段时间之后再来看看也是蛮不错的,也希望能帮助到正在学习的人们,本系列将要总结一下几点: 面向对象的编程思想 java的基本语法 一些有趣的框架解析 实战项目的整体思路 代码的优化以及性能调优的几种方案 整体项目的规划和视角 其它遗漏的东西

70.JAVA编程思想——Web应用

70.JAVA编程思想--Web应用 创建一个应用,令其在真实的Web 环境中运行,它将把Java 的优势表现得淋漓尽致.这个应用的一部分是在Web 服务器上运行的一个Java 程序,另一部分则是一个"程序片"或"小应用程序"(Applet),从服务器下载至浏览器(即"客户").这个程序片从用户那里收集信息,并将其传回Web 服务器上运行的应用程序.程序的任务非常简单:程序片会询问用户的E-mail 地址,并在验证这个地址合格后(没有包含空格,而

java编程思想学习(1)

Smalltalk 这是第一种成功的面向对象程序设计语言,也是Java 的基础语言.Smalltalk (java的基础语言)的五大基本特征: (1) 所有东西都是对象.可将对象想象成一种新型变量:它保存着数据,但可要求它对自身进行操作.理论上讲,可从要解决的问题身上提出所有概念性的组件,然后在程序中将其表达为一个对象. (2) 程序是一大堆对象的组合:通过消息传递,各对象知道自己该做些什么.为了向对象发出请求,需向那个对象"发送一条消息".更具体地讲,可将消息想象为一个调用请求,它调

67.JAVA编程思想——套接字

67.JAVA编程思想--套接字 "套接字"或者"插座"(Socket)也是一种软件形式的抽象,用于表达两台机器间一个连接的"终端".针对一个特定的连接,每台机器上都有一个"套接字",可以想象它们之间有一条虚拟的"线缆".线缆的每一端都插入一个"套接字"或者"插座"里.当然,机器之间的物理性硬件以及电缆连接都是完全未知的.抽象的基本宗旨是让我们尽可能不必知道那些细节.

73.JAVA编程思想——JDBC

73.JAVA编程思想--JDBC 据估算,将近一半的软件开发都要涉及客户(机)/服务器方面的操作.Java 为自己保证的一项出色能力就是构建与平台无关的客户机/服务器数据库应用.在Java1.1 中,这一保证通过Java 数据库连接(JDBC)实现了. 数据库最主要的一个问题就是各家公司之间的规格大战.确实存在一种"标准"数据库语言,即"结构查询语言"(SQL-92),但通常都必须确切知道自己要和哪家数据库公司打交道,否则极易出问题,尽管存在所谓的"标准

异常笔记--java编程思想

开一个新的系列,主要记一些琐碎的重要的知识点,把书读薄才是目的...特点: 代码少,概念多... 1. 基本概念 异常是在当前环境下无法获得必要的信息来解决这个问题,所以就需要从当前环境跳出,就是抛出异常.抛出异常后发生的几件事: 1.在堆上创建异常对象. 2.当前的执行路径中止                                          3. 当前环境抛出异常对象的引用.                                         4. 异常处理机制接

《Java编程思想》第十三章 字符串

<Java编程思想>读书笔记 1.String作为方法的参数时,会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置,从未动过. 2.显式地创建StringBuilder允许预先为他指定大小.如果知道字符串多长,可以预先指定StringBuilder的大小避免多次重新分配的冲突. 1 /** 2 * @author zlz099: 3 * @version 创建时间:2017年9月1日 下午4:03:59 4 */ 5 public class UsingStringBuilder {