传递属性
前面我们讲到RMI可以传递属性并简单介绍了一下一个有关开支报告程序的情况下面我们将深入讨论如何设计这样的系统这样介绍的目的是使您能够利用RMI的功能将属性从一个系统传递到另一个系统并随心所欲地安排当前的计算地点并便于将来的改变下面的例子并未涉及真实世界可能发生的所有问题但可帮助读者了解处理问题的方法
服务器定义的策略
图是可进行动态配置的开支报告系统的示意图客户机向用户显示图形用户界面(GUI)用户填写开支报告客户机程序使用RMI与服务器进行通信服务器使用JDBC( Java关系数据库连接包)将开支报告存储在数据库中至此这看起来与其它多层次系统大同小异但有一个重大区别 RMI能下载属性
假定公司关于开支报告的政策发生改变例如目前公司只要求对超过美元的开支需开具发票但到明天公司认为这太宽松了便决定除不超过美元的餐费以外任何开支均需开具发票如果不能下载属性的话那么在设计便于修改的系统时您可选择下列方法之一
用客户端安装与政策有关的程序政策改变时必须更新包含此政策的所有客户端程序您可在若干服务器上安装客户程序并要求所有用户从这些服务器之一运行客户程序从而减少问题但这仍不能彻底解决问题 那些让程序运行好几天的用户就无法使程序更新而总是会有一些用户为了方便而把软件复制到本地磁盘上
您可要求服务器在每次向开支报告添加项目时检查政策但这样就会在客户机和服务器之间产生大量数据流并增加服务器的工作量这还会使系统变得更加脆弱网络故障会立即妨碍用户而不仅仅是只在其呈交开支报告或启动新的报告时对其产生影响同时添加项目的速度也会变慢因为这需要穿越整个网络往返一圈才能到达(不堪重负的)服务器
您可在呈交报告时要求服务器对政策进行检查这样就会使用户创建很多必须待批报告的错误项目而不是立刻捕捉到第一个错误从而使用户有机会停止制造错误为避免浪费时间用户需要立刻得到有关错误的反馈
有了RMI您就能以简单的方法调用程序从服务器得到属性从而提供了一种灵活的方式将计算任务从服务器卸载到客户机上同时为用户提供速度更快的反馈当用户准备编写一份新的开支报告时客户机就会向服务器要求一个对象该对象嵌入了适用于该开支报告的当前政策就如同通过用Java编写的政策接口所表示的那样该对象可以以任何方式实现当前政策如果这是客户机RMI首次看到这种专门执行的政策就会要求服务器提供一份执行过程的副本如果执行过程将来发生变化则一种新的政策对象将被返回给客户机而RMI运行时则会要求得到新的执行过程
这表明政策永远是动态的您要想修改政策就只需简单地编写通用政策接口的新的执行程序把它安装在服务器上并对服务器进行配置以返回这种新类型的对象即可这样每台客户机都会根据新的政策对新的开支报告进行检查
这是一种比任何静态方法都更好的方法原因如下
所有客户机不必暂停或用新的软件来升级软件可根据需要在不工作时加以更新
服务器不必参与项目检查工作该工作可在本地完成
允许动态限制因为对象执行程序(而不仅仅是数据)是在客户机和服务器之间进行传递的
使用户能立刻看到错误
使客户机在服务器上所能调用的方法的远程接口定义如下
import javarmi*;
public interface ExpenseServer extends Remote {
Policy getPolicy() throws RemoteException;
void submitReport(ExpenseReport report)
throws RemoteException InvalidReportException;
}
import语句输入Java RMI包所有RMI类型均在包javarmi或其子包内定义接口ExpenseServer是一般的Java接口具有如下两种有趣的特点
它扩展了名为Remote的RMI接口这使该接口标记为可用于远程调用
它的所有方法均抛出RemoteException后者用来表示网络或信息故障
远程方法还可抛出您所需要的任何其他例外但至少必须抛出RemoteException这样您才能处理只会在分布式系统中发生的错误状态该接口本身支持两个方法getPolicy (返回一个实现政策接口的对象)和submitReport (提交一个完成的开支请求并在报告无论因何种原因使表格出现错误时抛出一个例外)
政策接口本身可声明一种使客户机知道能否在开支报告中添加一个项目的方法
public interface Policy {
void checkValid (Expenseentry entry)
throws PolicyViolationException;
}
如果该项目有效即符合当前政策则该方法可正常返回否则就会抛出一个描述该错误的例外政策接口是本地的(而非远程的)所以将通过本机对象在客户机上执行在客户机的虚拟机上而非整个网络上运行的对象客户机可能运行下列程序
Policy curPolicy = servergetPolicy();
start a new expense report
show the GUI to the user
while (user keeps adding entries) {
try {
curPolicycheckValid(entry); // throws exception if not OK
add the entry to the expense report
} catch (PolicyViolationException e) {
show the error to the user
}
}
serversubmitReport(report);
当用户请求客户机软件启动一份新的开支报告时客户机就调用servergetPolicy并要求服务器返回一个包含当前开支政策的对象添加的每个项目首先都被提交给该政策对象以获得批准如果政策对象报告无错误则该项目就被添加到报告中否则错误就被显示给用户而后者可采取修正措施当用户完成向报告中添加项目时整个报告就被呈交服务程序如下
import javarmi *;
import javarmiserver *;
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 (entrydollars() < 20) {
return; // no receipt required
else if (entry.haveReceipt() == false) {
throw new PolicyViolationException;
}
}
}
TodaysPolicy进行检查的目的是确保无收据的任何项目均少于20美元。TW.WInGWIT.cOm如果将来政策发生变化,仅少于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对象。这最后一种策略可使您像修改政策一样简单地修改开支报告的内容--当公司决定需要把餐费划分为早餐、午餐和晚餐项目,而且像上述修改政策一样简单地执行修改时--可编写一个实现该报告的新类,客户程序就会自动使用这个类。