传递属性 正如我们前面所说,RMI可以传递属性,并简要介绍了下一个支出报告程序。下面我们将深入讨论如何设计这样的系统。这个介绍的目的是使用RMI功能将属性从一个系统传输到另一个系统,并随意安排当前的计算位置,以促进未来的变化。下面的例子并不涉及现实世界中可能出现的所有问题,但它可以帮助读者理解如何处理问题。
定义服务器的策略
客户机向用户显示图形用户界面(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; }Javava输入import语句 RMI包。所有RMI类型都包java.定义rmi或其子包。所有RMI类型都包java.rmi或其子包定义。接口expenseserver是一种普通的Java接口,具有以下两个有趣的特点:它扩展了RMI接口,称为Remote,标记为远程调用。 所有的方法都抛出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); 当用户要求客户机软件启动新的支出报告时,客户机调用serverr.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类型 定义了远程对象类型的服务程序,在这种情况下,应该是单一的服务程序而不是复制服务(下面将详细介绍)。UnicastRemoteObject类型 定义了该服务程序的远程对象类型。在这种情况下,它应该是一个单一的服务程序,而不是复制服务(以下将详细介绍)。Java类Expenseverimpl实现远程连接Expenserver的方法。远程主机的客户机可以使用RMI将信息发送给Expenseserverimpl对象。本文讨论的重要方法是简单地返回定义当前政策的对象。以下是一个执行政策的例子: 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; } } } 写这一类,并将其安装在服务器上,然后告诉服务器开始提供tomorowspolicy对象,而不是dayspolicy对象,这样您的整个系统就会开始使用新的政策。当客户机调用服务器的getpolicy方法时,客户机的RMI会检查返回的对象是否已知。当客户机调用服务器的getpolicy方法时,客户机的RMI会检查返回对象是否为已知类型。当每台客户机第一次遇到tomorowspolicy时,RMI会在getpolicy返回之前下载政策。客户机可以很容易地开始加强这一新政策。
RMI使用标准Java对象序列化机制来传递对象。引用远程对象参数作为远程引用传递。如果向某种方法提供的参数是原始类型或本机(非远程)对象,则向服务器传输深副本。返回值也拾回返回值也拾回返回值也拾回返回值以同样的方式处理,只是沿着其他方向。RMI允许用户将完整的对象图传输并返回到本机对象,并将远程对象传输和返回引用。
在真实的系统中,getpolicy方法可能有一个参数,可以识别用户和支出报告类型(差旅、客户关系等)。),从而区分政策。您可能不需要单独的政策和费用报告对象,但您可以有一种newexpensereport方法,它可以返回直接检查政策的expensereport对象。这个最后一个策略可以让你像修改政策一样简单地修改费用报告的内容——当公司决定将餐费分为早餐、午餐和晚餐项目,并像上述修改政策一样简单地执行修改时——可以编写一个新的类别来实现报告,客户程序将自动使用这个类别。
![](/images/780-200-2.jpg)