深度探索Java RMI远端对象传递技术

类别:Java 点击:0 评论:0 推荐:

1、前言

RMI即java remote method Invocation(远端方法调用),它提供了针对java对象分布式计算的一种简单而直接的模型。这些java对象可能是新的java对象,也可能是围绕已有API的一些java外覆类。Java信奉“Write once, run everywhere”模型,RMI扩展了这种能够“run everywhere”的模型。

因为RMI是以java为核心的,所以它把java的安全性和便携性融入到了分布式计算中。你可以移动行为对象(behavior)(比如代理和商业逻辑)到你的网络,当你把java用到你的系统中时,RMI将带给你java所有的优越性。

RMI可以通过JNI(java native interface)连接到已有系统,它也可以通过JDBC来连接到传统的关系数据库,RMI/JNI和RMI/JDBC的结合使得你可以用RMI和已有的非java服务进行交互,同时当你扩展你的服务时,它能使你拥有java所有的优越性。

2、Java RMI 的优点

从根本上讲,RMI就是java的远端调用机制(RPC),但是由于RMI是java面向对象方法的一部分,所以相对于传统的RPC系统它有一些优势。传统的RPC系统是一种混合语言(中性语言),因此它们不能提供服务于所有可能的目标平台上。

RMI完全采用java来实现,通过原生方法调用来与已有系统建立连接,这意味着RMI能够采取一种自然、直接而强有力的方法来提供给你分布式的计算技术,从而可以把java功能以一种渐增的,无缝的方式添加到你的系统中去。

RMI的基本优点:

面向对象RMI能够将任何对象作为参数和返回值,而不仅仅是java预先定义好的那些类型。这意味着你能够直接传递任何java类型,比如说直接将一个java hashtable对象作为参数传递。如果是在现有RPC的系统中,你将不得不在客户端将这样一个对象分解为java数据基本类型,传到服务器端后,再重建一个hashtable类型。

可传递对象RMI能够实现从客户端到服务器端或者从服务器端到客户端对象(接口的实现)的传递。比如,你可以定义一个接口来考察雇员的开支报告,从而检测他们是否遵守当前的公司政策。当一个开支报告创建的时候,一个实现该接口的对象可能被客户端从服务器得到,而当政策改变的时候,服务器端将返回一个不同的对象,这个对象实现的是新的政策。这将提供你很大的灵活性,因为改变一个政策仅仅要求你写一个新的java类并把它安装到服务器的主机上。

设计模式能够传递对象使得你能够在分布式计算中运用所有的面向对象的技术,如两层(two tiers)或三层(three tiers)的系统。当你能够传递对象的时候,你能够在你的解决方案中使用面向对象的设计模式。所有面向对象的设计模式都依赖于针对它们能力的的不同行为对象,不能传递完整的对象(类型和方法),设计模式所带来的种种优点将不复存在。

安全可靠:当用户下载实现方法时,RMI使用内建的java安全机制来保护用户的系统安全。RMI用定义的安全管理器来保护系统免受有害applet的侵袭,进而通过拒绝下载有害的代码来保护你的系统。在服务器端,服务器能够拒绝下载任何实现。

容易书写/容易使用:RMI使得书写java服务和访问服务的java客户端代码变得十分简单。一个远端接口实际上就是一个java接口。服务器仅仅只用三行代码就可将其宣布为一个java服务,否则就是一个普通的java对象,这种简单性使得你可以快速的为一个全范围的分布对象系统书写服务,从而快速拿出一个早期版本用于测试和评估。同时因为RMI程序的易于书写,它们也很容易维护。

3、传递对象的实现

我们在前面描述RMI如何传递对象时,曾经简短的谈了一下开支报告程序。下面是如何去设计这样一个系统的深一层的描述。我们这里所提供的描述只是告诉你如何使用RMI的能力来将对象从一个系统传递到另外一个系统以及如何应付你对这种对象的改变。下面的这些例子也不能处理现实生活中的所有情况,它只是带你去尝试一下如何去处理这类问题。

 

                        图1 开支报告系统总架构图

图1显示了如此一个可动态构造的开支报告系统的总架构图,一个客户端显示一个GUI(graphical user interface)给用户,用户对这个开支报告进行填写。客户与服务器通过RMI来进行交流。 服务器通过JDBC(java 关系数据库包)将这些报告储存在数据库中,到目前为止,它可能象任何多层系统,事实上,它们之间存在一个显著的差别——RMI能够下载接口实现。

假定公司关于开支报告的政策发生了改变,例如,今天公司只要求开支大于20美元的发票,明天它可能觉得这样太过仁慈,它要求所有开支的发票,不能下载接口实现,要设计如此一个会时常发生改变的动态系统,你不得不使用下面的方法。

◆ 在客户端安装政策。一旦政策发生发生改变,则要求所有的包含政策的客户端对政策进行更新,当然你可以通过安装客户端在一些服务器上并且要求所有用户从这些服务器中的某一个去运行客户端来缓解这类问题。但是这仍然不能完全解决这类问题,如果某个人一直把程序开着,则在程序运行的几天内它将得不到更新,除非他重新启动程序。

◆ 当任何条目要增加到开支报告中时,你需要通过服务器端来进行核对,这样一来,将会造成服务器和客户端之间的频繁交互,从而增加网络负担甚至阻塞网络。同时它也使得你的网络变得更加脆弱,一个网络的失败可能影响到所有的客户端,而不仅仅是那些提交报告或是开启一个新报告的人。它也意味着增加一个条目将会变得非常慢,因为它将要求一个回路通过网络抵达不堪重负的服务器。

◆ 或者你等到报告提交的时候才通过服务器来对报告进行核对,但是这样的话可能将创建许多坏的条目,这些条目然后被成批的反馈给用户,用户可能看到的都是一些同样的错误,相形之下,如果能够让用户捕捉到第一个错误,从而避免后面犯同样的错误显然是更为明智的做法。

通过RMI你可以采用远端方法调用来从服务器端下载接口实现,它提供了一种十分灵活的手段,我们可以由此缓解服务器端的运算压力,从而能够提供用户更快捷的反馈。当用户意图向服务器书写一个开支报告时,客户端得首先经由用java书写的接口来向服务器寻求一个体现当前关于开支报告政策的对象,该对象可以以任意方式来实现。如果这是客户端RMI首次得到政策接口实现的对象,RMI将向服务器索取一个该实现的拷贝,如果该政策明天发生了改变,一个新的政策对象将返回到客户端,RMI也将索取这个新的实现。

这意味着政策总是动态的。你可以通过改写政策接口的实现来改变政策,把它安装到服务器上,然后调整服务器端让它返回这个新类型的对象。从那一刻开始,任何的开支报告将采用新的政策进行检查。

这是一个比任何静态途径更好的办法,因为:

◆  服务器更新之后,所有的客户端不必终止和进行软件更新。

◆  服务器不将因那些本就可以在客户端进行的检查动作而加重负担。

◆  由于数据和对象的实现可以在客户端和服务器端进行传递,所以政策的改变变得十分容易。

◆  能够迅速的将错误返回给用户,从而可以即时避免犯下同样的错误。

下面是服务器端定义的方法的远程接口,客户端可以调用该方法。

import java.rmi.*;

public interface ExpenseServer extends Remote

{

    Policy getPolicy() throws RemoteException;

    void   submitReport(ExpenseReport report)

                throws RemoteException, InvalidReportException;

}

第一行import java的RMI包,所有的RMI类型都定义在包java.rmi或其子包内。接口ExpenseServer是一个标准的标准的java接口,其具备两个有趣的特征:

◆  从接口Remote派生,这表明该接口可以被远端调用。

◆  该接口的所有方法都抛出RemoteException异常,它被用来标识网络和消息传送失败。Remote方法可以掷出任何你喜欢的异常,但是它们至少必须掷出RemoteException异常,以便你可以处理那些只会在分布系统中才会产生的错误情况。这个接口本身支持两个方法:getPolicy返回一个实现Policy接口的对象,submitReport方法提交一个完成的报告,如果报告有任何错误,它将抛出一个异常。

    Policy接口本身宣布了一个方法,该方法用来检查客户端是可以把一个条目增加到报告中去。 

public interface Policy

{

    void checkValid(ExpenseEntry entry)

        throws PolicyViolationException;

}

   如果条目是有效的,即其与当前政策是吻合的,该方法将正常返回,否则它将抛出一个描述错误类型的异常。Policy接口是本地的(非远端的),也就是说它将直接在客户端的虚拟机上运行,不需要通过网络,一个客户端的操作将是象这样的:

Policy curPolicy = server.getPolicy();

start a new expense report

show the GUI to the user

while (user keeps adding entries)

{

try {

        curPolicy.checkValid(entry); // throws exception if not OK

        add the entry to the expense report

}

catch (PolicyViolationException e) {

        show the error to the user

    }

}

server.submitReport(report);

当客户端调用客户端软件启动一个新的开支报告时,客户端调用服务器的Server.getPolicy方法请求服务器返回一个体现当前Policy的对象。每一个待添加的对象首先被提交给Policy对象去证明其合法性。如果Policy对象没有返回错误,该条目就可以增加到报告中,否则,错误将被反馈给用户,用户便可以依据其来采取正确的措施即时消除错误。当用户完成了对报告的添加后,整个报告将被提交,服务端代码类似下面这样:

import java.rmi.*;

import java.rmi.server.*;

class ExpenseServerImpl extends UnicastRemoteObject implements ExpenseServer

{

    ExpenseServerImpl() throws RemoteException {

        // ...set up server state...

    }

    public Policy getPolicy() {

        return new TodaysPolicy();

    }

    public void submitReport(ExpenseReport report) {

        // ...write the report into the db...

    }

}

除了基本的rmi包外,我们还导入了RMI.server包。类UnicastRemoteObject定义了当前服务的类型,在这个例子中是单例服务。Java类ExpenseServerImpl实现了远端接口ExpenseServer的方法。远端的客户主机可以用RMI发送消息给ExpenseServerImpl对象。

   这个例子里的重要方法是getPolicy,它仅仅返回一个定义当前Policy的对象。下面我们来看看一个Policy接口实现的例子:

  public class TodaysPolicy implements Policy {

    public void checkValid(ExpenseEntry entry)

         throws PolicyViolationException

    {

         if (entry.dollars() < 20) {

             return; // no receipt required

         } else if (entry.haveReceipt() == false) {

             throw new PolicyViolationException;

         }

    }

}

TodayPolicy 通过checkVailid方法来确保任何没有发票的条目都小于20美元,如果Policy明天发生了改变,其规定除了大于20美元的餐饮费外,其余都需要开发票,这时你可以提供一个新的Policy接口的实现:

 public class TomorrowsPolicy implements Policy {

    public void checkValid(ExpenseEntry entry)

         throws PolicyViolationException

    {

         if (entry.isMeal() && entry.dollars() < 20) {

             return; // no receipt required

         } else if (entry.haveReceipt() == false) {

             throw new PolicyViolationException;

         }

    }

}

写这个类,将其安装在服务器上,从而告知服务器从现在开始不再返回TodayPolicy对象,而是返回TomorrowsPolicy对象,由此你的整个系统将得到更新。当客户端调用服务器的getPolicy方法时,客户端的RMI将检查是否返回的是一个已知类型的对象。每个客户端第一次遇到一个TomorrowObject对象时,RMI将在getPolicy返回之前下载Policy新的实现,客户端不废一兵一卒即可使用新的对象。

RMI使用标准java对象序列化机制来传递对象,远端对象引用类型的参数将以远端引用来传递,如果方法的参数是基本类型或者局部类型(非远端),就将其拷贝传至服务器。返回值以同样方式进行处理,同时,在另一方面,RMI允许你返回对远端对象的引用。

在一个实际的系统中,getPolicy方法可能会设置一个参数来识别用户和开支报告(旅行,应酬等)的种类以便Policy可以进行区分。如果你不打算使用有差别的Policy和expense report对象,你可以使用一个newExtenseReport方法来直接返回一个检查Policy的ExpenseObject对象。这种策略将使你改变一个expense report的内容与修改一个Policy一样容易,如果哪一天公司决定把meals分割成breakfast,lunch,和dinner,改变起来将与上面所示修改一个Policy一样容易,你只需要写一个新类实现这个report,客户端然后可以自动使用它。

本文地址:http://com.8s8s.com/it/it16893.htm