利用RMI实现JAVA分布式应用方案


利用RMI实现JAVA分布式应用方案

Java RMI (Remote Method Invocation 远程方法调用)是用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实现Java分布式应用。
一、RMI系统运行机理

RMI应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户机调用这些远程对象的方法。而典型的客户机程序则从服务器中得到一个或多个远程对象的引用,然后调用远程对象的方法。RMI为服务器和客户机进行通信和信息传递提供了一种机制。

在与远程对象的通信过程中,RMI使用标准机制:stub和skeleton。远程对象的stub担当远程对象的客户本地代表或代理人角色。调用程序将调用本地stub的方法,而本地stub将负责执行对远程对象的方法调用。在RMI中,远程对象的stub与该远程对象所实现的远程接口集相同。

调用stub的方法时将执行下列操作:(1) 初始化与包含远程对象的远程虚拟机的连接(2) 对远程虚拟机的参数进行编组(写入并传输);(3) 等待方法调用结果;(4) 解编(读取)返回值或返回的异常;(5) 将值返回给调用程序。为了向调用程序展示比较简单的调用机制,stub将参数的序列化和网络级通信等细节隐藏了起来。在远程虚拟机中,每个远程对象都可以有相应的skeleton(在JDK1.2环境中无需使用skeleton)。

Skeleton负责将调用分配给实际的远程对象实现。它在接收方法调用时执行下列操作:(1) 解编(读取)远程方法的参数;(2) 调用实际远程对象实现上的方法;(3) 将结果(返回值或异常)编组(写入并传输)给调用程序。stub和skeleton由rmic编译器生成。

利用RMI编写分布式对象应用程序需要完成以下工作:(1) 定位远程对象。应用程序可使用两种机制中的一种得到对远程对象的引用。它既可用RMI的简单命名工具rmiregistry来注册它的远程对象,也可以将远程对象引用作为常规操作的一部分来进行传递和返回。(2)与远程对象通信。远程对象间通信的细节由RMI处理,对于程序员来说,远程通信看起来就像标准的Java方法调用。(3)给作为参数或返回值传递的对象加载类字节码。因为RMI允许调用程序将纯Java对象传给远程对象,所以,RMI将提供必要的机制,既可以加载对象的代码又可以传输对象的数据。在RMI分布式应用程序运行时,服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器上的注册服务程序中用远程对象的名字查找该远程对象,然后调用它的方法。

二、对象序列化

在RMI分布式应用系统中,服务器与客户机之间传递的Java对象必须是可序列化的对象。不可序列化的对象不能在对象流中进行传递。对象序列化扩展了核心Java输入/输出类,同时也支持对象。对象序列化支持把对象编码以及将通过它们可访问到的对象编码变成字节流;同时,它也支持流中对象图形的互补重构造。序列化用于轻型持久性和借助于套接字或远程方法调用(RMI)进行的通信。序列化中现在包括一个 API(Application Programming Interface,应用程序接口),允许独立于类的域指定对象的序列化数据,并允许使用现有协议将序列化数据域写入流中或从流中读取,以确保与缺省读写机制的兼容性。

为编写应用程序,除多数瞬态应用程序外,都必须具备存储和检索 Java对象的能力。以序列化方式存储和检索对象的关键在于提供重新构造该对象所需的足够对象状态。存储到流的对象可能会支持 Serializable(可序列化)或 Externalizable(可外部化)接口。对于Java对象,序列化形式必须能标识和校验存储其内容的对象所属的 Java类,并且将该内容还原为新的实例。对于可序列化对象,流将提供足够的信息将流的域还原为类的兼容版本。对于可外部化对象,类将全权负责其内容的外部格式。序列化 Java 对象的目的是:提供一种简单但可扩充的机制,以序列化方式维护 Java对象的类型及安全属性;具有支持编组和解编的扩展能力以满足远程对象的需要;具有可扩展性以支持 Java 对象的简单持久性;只有在自定义时,才需对每个类提供序列化自实现;允许对象定义其外部格式。

三、分布式应用的实现和运行步骤

编写Java RMI分布式应用程序的步骤主要包括以下几步:

(1) 将远程类的功能定义为Java接口。在Java中,远程对象是实现远程接口的类的实例。在远程接口中声明每个要远程调用的方法。远程接口具有如下特点:1) 远程接口必须声明为public。如果不这样,则除非客户端与远程接口在同一个包内,否则当试图装入实现该远程接口的远程对象时会得到错误结果。2) 远程对象扩展java.rmi.Remote接口。3) 除了所有应用程序特定的例外之外,每个方法还必须抛出java.rmi.RemoteException例外。4) 任何作为参数或返回值传送的远程对象的数据类型必须声明为远程接口类型,而不是实现类。

(2) 编写和实现服务器类。该类是实现(1)中定义的远程接口。所以在该类中至少要声明实现一个远程接口,并且必须具有构造方法。在该类中还要实现远程接口中所声明的各个远程方法。

(3) 编写使用远程服务的客户机程序。在该类中使用java.rmi.Naming中的lookup()方法获得对远程对象的引用,依据需要调用该引用的远程方法,其调用方式和对本地对象方法的调用相同。

实现了服务器和客户机的程序后,就是编译和运行该RMI系统。其步骤有:
(1) 使用javac编译远程接口类,远程接口实现类和客户机程序。
(2) 使用rmic编译器生成实现类的stub和skeleton。
(3) 启动RMI注册服务程序rmiregistry。
(4) 启动服务器端程序。
(5) 启动客户机程序。

四、实例分析

本文以一个实例来说明怎样编写RMI分布式应用系统。该实例用于实现一个三层的Client/Server程序,即包括数据库服务器、应用服务器和客户机三部分。其功能是让应用服务器把客户机发出的数据库查询请求传送到数据库服务器,数据库服务器再把数据库查询操作的结果送回应用服务器,然后送到客户端显示。整个系统包括服务器端和客户端程序,但这只是逻辑上的划分,其实它们都位于同一个机器上,即Web服务器。在客户端是一个Applet,通过它获得远程对象的引用,并调用远程对象的方法。而应用服务器上的程序其主要的功能是完成对数据库连接的配置、数据库的查询操作和把查询结果转换成可序列化的RecSet对象,当客户端调用远程对象的远程方法时就把它作为参数或返回值传递给客户端,实现服务器与客户机的通信。客户端得到查询结果后把它显示出来。在应用服务器和数据库服务器之间是通过Java的JDBC来连接的,本作者使用的是Sybase公司提供的JDBC Driver-jConnect5.0来实现对数据库的连接的。应用服务器同时也是Web服务器,因此只要上网的计算机通过浏览器就可以下载客户端的Applet程序到本地机上运行。具体实现步骤和部分程序代码如下。

用RMI开发基于Java的企业分布式应用

概述

  随着电力企业信息化建设的不断深入和发展,企业内部和企业与企业之间对信息、对数据的交换量大大增加,这些信息与数据越来越需要在不同的计算机网络间传送和交流。同时,由于各单位、各部门之间的现存的计算机网络硬件设备与操作系统千差万别,应用水平也参差不齐,因此,开发出跨平台、可移植、高效安全的网络分布式应用来服务于电力企业,就显得尤为重要。

  在当今的编程术语里,分布式计算已经成为很常见的词,它将企业的业务数据和程序分布在网络的不同物理位置上,通过调动网络上多台计算机的处理能力,发挥远程调用数据的功能。

  远程方法调用(Remote Method Invocation ,RMI),可以在不同的Java虚拟机(JVM)之间实现对象与对象的通信。JVM可以位于相同或不同计算机上,在多个JVM中,一个JVM可以调用存储在其它JVM的对象的方法。

  本文主要介绍RMI的特点,分析应用RMI进行企业分布式计算的原理,以及利用RMI实现基于Java的企业分布式应用的具体步骤。

  远程方法调用(RMI)的特点

  1、TCP编程的缺点

  由于Java编程语言设计之初就是面向对象和支持网络的,因此,基于对象的RMI机制已经内置在Java平台中。

  我们经常会在网络开发中使用TCP/IP编程,这样,自然而然地就会涉及到Socket(套接字)编程。但是,使用Socket编程需要大量重复编码,在复杂分布式操作时显得非常麻烦,而且易于出错。因此,如何快速、高效、安全、可扩展地进行网络分布式计算,是开发者们一贯追求和倡导的主题。直到RMI的出现,这种繁杂、低效的开发情况才有很大改观。

  2、RMI编程的特点

  当我们利用对象序列化在网络上分配对象时,RMI提供了非Java平台无法匹敌的独特而强大的分布式计算模型,RMI主要有以下特点:

   客户机可以向本地方法一样调用远程服务器上的方法;

   可以根据接口指定客户机/服务器编程合约;

   可以从服务器对象缺省二进制类文件,自动生成调动/反调动代码;

   将Java编程模型扩展到机器边界(和Java虚拟机(JVM)边界之外),不需要任何特殊语法;

   还可以和一个远程方法调用中的数据同时传输行为(代码)。

  尽管RMI不是唯一的企业级远程对象访问方案,但它却是最容易实现的。

  3、RMI与CORBA

  作为分布式应用程序框架的规范,COBRA首当其冲,它是由对象管理组织(OMG)开发的。与CORBA不同的是,CORBA能够利用不同编程语言(例如C/C++、Basic等)开发实现分布式应用,而RMI是一种纯Java解决方案。在RMI中,程序的所有部分都由Java语言编写,这样,开发出来的程序完全符合Java规范,便于实现跨平台访问、扩展和移植。按照笔者所在西北电力建设集团公司的情况看,服务器操作系统主要有Linux和Windows2000 Server,分别存在于公司和部门当中,它们是不同的系统平台;同时,公司下属各个工程项目部又距离很远,近的几十公里,远则达到上千公里甚至位于国外,因此跨平台和远程访问这两大功能在开发企业应用系统时就必须考虑,而RMI恰恰能够用它的自身特点来满足编程需要。

  RMI基本体系结构简介

  RMI通过TCP/IP在内部使用Socket,象其名称暗示的那样,它能够帮助我们查找并执行远程对象的方法。RMI的目的是让位于不同JVM中的对象,在外观及行为上都像是本地的对象。

  通常,我们把调用这种远程对象的JVM,称为客户机;而把包括这种远程对象的JVM,称为服务器。

  尽管对一个远程对象的引用和获得对本地对象的引用有所不同,但我们可以把远程对象像本地对象一样使用。应用程序并不知道一个对象是远程的还是本地的。实际上,远程对象上被调用的方法与本地对象上调用的方法,具有相同的语法结构。

  作为RMI的底层(会包含复杂的Socket操作),它会自动截获方法调用,找到远程对象,然后处理远程请求。笔者认为,RMI设计的重要之处,就在于不但在设计上实现了远程访问功能,而且实现了设计的透明性。

  RMI的基本体系结构,概括起来说,由三个抽象层组成:

  1、存根/框架层(Stubs/Skeletons Layer)

  RMI为我们引入了两种特殊类型的对象,称为存根(Stub)和框架(Skeleton),它们组成了RMI的第一层。

  在远程通信的时候,要利用TCP/IP协议,做很多底层数据的打包传输。运用Java技术,我们先要把数据或者对象转换成字节流(byte stream),便于网络传输,这个过程叫汇集(marshaling);当收到远程传来的字节流后,我们要把流信息转换成对象或者数据,这个过程叫解读(unmarshaling),它与汇集刚好相反。

  Stub和Skeleton层位于实际应用程序之下,建立在Proxy(代理)设计方案之上。Stub类的作用是远程服务器实现的代理的角色,Stub是客户方对象;Skeleton类用于帮助对象通过RMI链接与Stub通信,它从链路中读取方法调用的参数,向远程服务实现对象进行调用,接受返回值,然后再把返回值写回到Stub。

  2、远程引用层(Remote Reference Layer)

  远程引用层定义和支持着RMI连接的调用语义(semantics)。

  RMI进行远程访问要用到JRMP(Java Remote Method Protocol,即Java远程方法协议),这一层提供专用于JRMP的RemoteRef对象,它位于java.rmi.server包内,代表着远程对象的一个句柄。RemoteRef使用远程引用来执行远程对象的一个远程方法调用。

  3、传输层(Transport Layer)

  传输层在JVM之间建立基于流的网络连接,并且负责设置和管理这些连接。这时候,RMI使用一种线级(wire-level)协议进行基于TCP/IP的连接,该协议就是Java远程方法协议(JRMP,即Java Remote Method Protocol)。

  在JDK版本1.2开始,JRMP不再需要Skeleton,而是使用reflection来建立与远程服务的连接。为了生成Stub,我们须用rmic。
当前的RMI实现中,传输层建立在TCP/IP基础上,设计用于在客户和服务器之间建立一条连接(即使联网有障碍)。

开发的基本步骤

  我们使用RMI编写Client/Server模式(客户/服务器)应用程序,包括6个基本步骤:

  1) 定义远程接口

  2) 实现远程接口

  3) 准备远程调用的服务器对象

  4) 生成残根Stub(客户代理)和框架Skeleton(服务器实体)

  5) 用rmiregistry找到远程对象

  6) 运行测试RMI分布式应用

  开发企业信息发布系统实例

  在开发RMI进行分布式访问之前,需要将各项功能模块化,即把实际应用抽象成符合Java规范的类和接口模型,使这些类和接口之间互相协作,能实现各自独立的功能,最后,可以把它们组合成统一的网络分布式系统。

  现在,我们就以开发公司信息发布系统为例,把主模块(主要的类文件)的名称暂定为InfoDistributeService(信息发布服务),为了保持应用开发的数据一致性和清晰度,接下来涉及的其它模块命名也将以这个模块命名为基准。

  1、定义远程接口

  Java RMI运行环境要求任何可以远程调用的方法必须放在远程接口中。

  该远程接口用来扩展java.rmi.Remote接口,在Java API中,可以发现它没有任何方法,只是个标志性接口,这样,可以让Java运行环境(JRE)认识每个接口的特殊属性,以便能够远程访问。

  因此,按照信息发布服务的命名(InfoDistributeService),首先须将InfoDistributeRemote定义为远程接口,同时仅放入一个供测试的方法 getRemoteInfo()来实现编码,将所有模块至于新建的enterprise.distribute包中,代码如下:






// -----------InfoDistributeRemote.java-------------------
package enterprise. distribute;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface InfoDistributeRemote extends Remote{
public String getRemoteInfo() throws RemoteException;
}


  2、实现远程接口

  这是一个实现远程对象的类。如果实现了远程接口,就能够覆盖(override)该对象中的所有方法,因此,远程对象的实现类将真正包含我们希望导出的方法的代码。

  在远程信息发布系统中,我们至少实现一个远程接口的对象,它就是远程可访问的对象。这里,InfoDistributeService类可以为我们生成远程可访问对象的实例:






// -----------InfoDistributeService.java------------------
package enterprise. distribute;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class InfoDistributeService
extends UnicastRemoteObject implements InfoDistributeRemote{
 public InfoDistributeService() throws RemoteException{
  super();
 }
 // The return value of the method only for testing...
 public String getRemoteInfo(){
  return "Hello! I am a remote object.";
 }
}


  InfoDistributeService类实现远程接口InfoDistributeRemote,并继承java.rmi.server.UnicastRemoteObject。由于符合InfoDistributeRemote接口,因此该类除构造方法之外,还应有getRemoteInfo()方法,而且必须将该方法实现。

  同时我们注意到,getRemoteInfo()方法抛出了java.rmi.RemoteException异常。由于远程方法调用过程中,要进行很多的低级网络操作,因此网络错误可能在调用过程中随时发生,这样,远程接口中的每个方法(尽管这里只有一个getRemoteInfo()方法)都必须抛出RemoteException异常,而且,java.rmi.RemoteException都要在代码中显式处理,即将所有RMI活动涉及的代码都要放在try-catch块中。

  3、准备供远程调用的服务器对象

  这是一个作为服务器使用的类,它是相对于要访问远程方法的客户端而言的。它存储着绑定的字符串和对象。

  将远程对象设置成接受远程调用就像启动了侦听ServerSocket对象的socket服务器。事实上在使用RMI时,TCP/IP协议的传输方式,在幕后发生了很多底层网络操作,但是此刻,用户不需要知道这些细节,只需要逐步导出远程对象:

  在Java开发工具的较新版本JDK 1.4中,可以选择java.rmi.UnicastRemoteObject或者java.rmi.Activation.Activatable。其中,更多用到的是UnicastRemoteObject,因为它顾名思义,就是在客户机与服务器对象实例之间建立一对一连接。

  4、生成Stub和Skeleton

  由RMI生成的调动与反调动的远程调动代码也和Java的其它代码一样,都包含在后缀是.class的文件中。

  运用RMI之后,在客户机上生成的调动参数和反调动返回值的代码称为残根(Stub),服务器上生成的反调动参数和进行实际方法调用调动返回值的代码称为框架(Skeleton)。

  可以使用RMI自带的命令行工具rmic(即RMI Compiler),先扫描远程对象的.class文件,随之生成残根与框架代码。工具rmic的原理如图1。









  5、远程客户端:这是一个帮助我们访问远程方法提供帮助的类,它也是最终用户。我们将使用查找和调用远程方法的方法在该类中调用远程方法。


  典型的rmic调用如下(在当前目录): 我们有了远程接口和实现,就可以用rmic对类进行编译,生成Stub和Skeleton,在命令行窗口中使用以下代码行:






// compile all java source files
javac enterprise\distribute\*.java
// make stub and skeleton code
rmic enterprise.distribute.InfoDistributeService


  运行完毕后,在当前目录生成下列文件(即调动代码):



5、用rmiregistry找到远程对象

  导出服务器方的对象之后,就可以远程访问,但客户机还要设法与这些远程对象取得联系。

  由于分布式应用程序可能涉及许多不同机器,因此通信的所有机器对之间需要建立初始连接,换句话说,就是要设法找到初始远程对象,这项工作通过rmiregistry命令执行。

  JDK开发工具提供了实用程序RMI Registry,用来维护文本名和远程对象之间的映射,可以进行远程访问。

  在客户机方,RMI注册表可以通过同一java.rmi.Naming类的lookup()静态方法用程序访问。例如,远程主机为iServer,远程对象实例为InfoDistributeService实例,远程端口号为5678,则客户机如果查找远程主机iServer中的远程InfoDistributeService实例,那么引用实例时须使用远程接口InfoDistributeRemote,代码如下:






InfoDistributeRemote iServer=( InfoDistributeRemote)Naming.lookup("rmi://iServer:5678/InfoDistributeService");


  在服务器方,导出的远程对象可以通过java.rmi.Naming类在本地注册rmiregistry的运行实例。例如,用rebind()方法将iService中的InfoDistributeService的实例与名称InfoDistributeService相关联。






InfoDistributeService iService=new InfoDistributeService();
Naming.rebind("/ InfoDistributeService",iService);


  综上所述,InfoDistributeRemote是个Java接口,因此iServer实际上是实现InfoDistributeRemote接口的本地对象实例,它不在远程,而是远程InfoDistributeService的本地表示。也就是说,它是由前述rmic工具自动生成的残根码InfoDistributeService_Stub.class的本地实例。图3显示了远程对象调用的工作原理。








图2 远程对象调用的工作原理


  从图2可以看出,客户机实现通过iServer变量维护远程对象的调用。事实上,iServer引用变量是实现InfoDistribute接口的对象的本地引用,该对象是InfoDistributeService残根实现。这个残根和服务器方的框架一起通过InfoDistributeRemote接口调动/反调动所有远程调用。

  当我们把InfoDistributeService看成是实现InfoDistributeRemote接口的本地实例的时候,残根代码只在幕后进行工作,而实现这一切则变得非常透明。

  由上述原理,可以进一步设计完善客户机和服务器代码,进而编写出完整的应用程序。

  6、运行测试RMI分布式应用

  在确认已经设计好必须的几大类模块后,我们开始按以下步骤,运行并测试该信息发布系统的基本功能(即仅仅实现远程接口中声明的getRemoteInfo()方法)。

   在前面定义的包enterprise.distribute中,执行javac命令编译后缀为.java的源文件。






javac enterprise/distribute/*.java


   接着,用RMIC工具生成残根与框架。






rmic enterprise.distribute.InfoDistributeService


   编译之后,需要确定客户机与服务器发行版本的内容。因此,需用jar命令,将客户机与服务器发行版本包装成 .jar文件。 其中,包装服务器的文件命令如下:






jar cvf InfoDistributeService.jar
enterprise\distribute\InfoDistributeService.class
enterprise\distribute\InfoDistributeRemote.class
enterprise\distribute\InfoDistributeService_Stub.class
enterprise\distribute\InfoDistributeService_Skel.class


  同样,包装客户机的类似于以上命令。

  运行RMI应用程序

  完成了第一阶段的所有RMI试验,然后运行信息发布应用程序。按照Java规范,需要依次启动下列项目:

  启动RMIRegistry

  在代码目录中用以下命令启动rmiregistry实例,使之在控制台开始运行。






start rmiregistry


  启动服务器

  直接启动服务器,生成InfoDistributeService远程对象的实例,并向注册表注册。






start java -classpath InfoDistributeService.jar
enterprise.distribute.InfoDistributeService


  启动客户机

  最后,用java -classpath RemoteClient.jar 命令启动客户机,然后通过rmiregistry找到远程信息发布服务,再通过远程调用得到所需要的远程信息。

  结论

  本文简要阐述了Java RMI的特点,以及用RMI开发企业分布式应用的主要步骤。以远程信息发布系统为例,简要地说明了远程对象访问、远程方法调用在信息发布时的原理和实现过程。

  为了开发出更符合实际的企业分布式应用,RMI还可以结合对象序列化实现更加强大的功能,为我们开发更加灵活、高效的网络分布式应用系统提供方便。


没有评论: