|
传递属性
前面我们讲到,RMI可以传递属性,并简单介绍了一下一个有关开支报告程序的情况。下面我们将深入讨论如何设计这样的系统。这样介绍的目的是使您能够利用RMI的功能将属性从一个系统传递到另一个系统,并随心所欲地安排当前的计算地点,并便于将来的改变。下面的例子并未涉及真实世界可能发生的所有问题,但可帮助读者了解处理问题的方法。
服务器定义的策略
图1是可进行动态配置的开支报告系统的示意图。客户机向用户显示图形用户界面(GUI),用户填写开支报告。客户机程序使用RMI与服务器进行通信。服务器使用JDBC( Java关系数据库连接包)将开支报告存储在数据库中。至此,这看起来与其它多层次系统大同小异,但有一个重大区别-- RMI能下载属性。 假定公司关于开支报告的政策发生改变。例如,目前公司只要求对超过20美元的开支需开具发票。但到明天,公司认为这太宽松了,便决定除不超过20美元的餐费以外,任何开支均需开具发票。如果不能下载属性的话,那么在设计便于修改的系统时您可选择下列方法之一:
用客户端安装与政策有关的程序。政策改变时,必须更新包含此政策的所有客户端程序。您可在若干服务器上安装客户程序,并要求所有用户从这些服务器之一运行客户程序,从而减少问题。但这仍不能彻底解决问题-- 那些让程序运行好几天的用户就无法使程序更新,而总是会有一些用户为了方便而把软件复制到本地磁盘上。 您可要求服务器在每次向开支报告添加项目时检查政策。但这样就会在客户机和服务器之间产生大量数据流,并增加服务器的工作量。这还会使系统变得更加脆弱--网络故障会立即妨碍用户,而不仅仅是只在其呈交开支报告或启动新的报告时对其产生影响。同时,添加项目的速度也会变慢,因为这需要穿越整个网络往返一圈才能到达(不堪重负的)服务器。 您可在呈交报告时要求服务器对政策进行检查。这样就会使用户创建很多必须待批报告的错误项目,而不是立刻捕捉到第一个错误,从而使用户有机会停止制造错误。为避免浪费时间,用户需要立刻得到有关错误的反馈。 有了RMI,您就能以简单的方法调用程序从服务器得到属性,从而提供了一种灵活的方式,将计算任务从服务器卸载到客户机上,同时为用户提供速度更快的反馈。当用户准备编写一份新的开支报告时,客户机就会向服务器要求一个对象,该对象嵌入了适用于该开支报告的当前政策,就如同通过用Java编写的政策接口所表示的那样。该对象可以以任何方式实现当前政策。如果这是客户机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的RMI接口,这使该接口标记为可用于远程调用。
它的所有方法均抛出RemoteException,后者用来表示网络或信息故障。
远程方法还可抛出您所需要的任何其他例外,但至少必须抛出RemoteException,这样您才能处理只会在分布式系统中发生的错误状态。该接口本身支持两个方法:getPolicy (返回一个实现政策接口的对象),和submitReport (提交一个完成的开支请求,并在报告无论因何种原因使表格出现错误时,抛出一个例外)。 政策接口本身可声明一种使客户机知道能否在开支报告中添加一个项目的方法: public interface Policy { void checkValid (Expenseentry entry) throws PolicyViolationException; } 如果该项目有效--即符合当前政策,则该方法可正常返回。否则就会抛出一个描述该错误的例外。政策接口是本地的(而非远程的),所以将通过本机对象在客户机上执行--在客户机的虚拟机上,而非整个网络上运行的对象。客户机可能运行下列程序: 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,并要求服务器返回一个包含当前开支政策的对象。添加的每个项目首先都被提交给该政策对象,以获得批准。如果政策对象报告无错误,则该项目就被添加到报告中;否则错误就被显示给用户,而后者可采取修正措施。当用户完成向报告中添加项目时,整个报告就被呈交。服务程序如下: 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的服务程序包。类型UnicastRemoteObject 定义了此服务程序远程对象的类型,在本例中,应为单一服务程序而非复制服务(下面还会详细介绍)。Java类ExpenseSeverImpl实现远程接ExpenseServer的方法。远程主机的客户机可使用RMI将信息发送给ExpenseServerImpl对象。
本文中讨论的重要方法是getPolicy,它简单地返回定义当前政策的对象。下面看一个执行政策的例子:
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; } } } TodaysPolicy进行检查的目的是确保无收据的任何项目均少于20美元。如果将来政策发生变化,仅少于20美元的餐费可不受“需要收据”政策的限制,则您可提供新的政策实现: 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; } } } 编写这个类,并把它安装在服务器上,然后告诉服务器开始提供TomorrowsPolicy对象,而非daysPolicy对象,这样您的整个系统就会开始使用新的政策。当客户机调用服务器的getPolicy方法时,客户机的RMI就会检查返回的对象是否为已知类型。每台客户机首次遇到TomorrowsPolicy时,RMI就会在getPolicy返回前下载政策的实现。客户机可轻松地开始增强这个新的政策。
RMI使用标准Java对象序列化机制传递对象。引用远程对象参数作为远程引用传递。如果向某方法提供的参数为原始类型或本机(非远程)对象,则向服务器传递一个深副本。返回值也拾?照同样的方式处理,只不过是沿其它方向。RMI可使用户向本机对象传递和返回完整对象图并为远程对象传递和返回引用。
在真实的系统中,getPolicy方法可能会有一个可以识别用户及开支报告类型(差旅、客户关系等)的参数,这样可使政策加以区别。您或者可以不要求单独的政策和开支报告对象,但您可以有一种newExpenseReport方法,它可返回一个直接检查政策的ExpenseReport对象。这最后一种策略可使您像修改政策一样简单地修改开支报告的内容--当公司决定需要把餐费划分为早餐、午餐和晚餐项目,而且像上述修改政策一样简单地执行修改时--可编写一个实现该报告的新类,客户程序就会自动使用这个类。
| | |