可激活的 Jini 服务:实现 RMI 激活

来源:互联网 发布:淘宝买家好评语大全 编辑:程序博客网 时间:2024/04/30 06:15

RMI 激活框架能够产生自给自足的、灵活的 Jini 服务

002 年 3 月 16 日

经 JavaWorld 许可转发。Jini 服务必须长期存在且具有弹性,还必须几乎无需用户介入就能有效地管理其计算资源。Jini 服务开发者可以使用 RMI 激活框架(RMI activation framework)来实现这些目标。可激活的服务通过在计算资源不在使用中时取消激活它们,在有客户机访问时重新激活它们来管理计算资源。此外,可激活的对象在服务多次重新启动间维护持久引用。本文展示如何使用 RMI 激活管理计算资源及提高 Jini 服务的可用性。

主要软件供应商的系统体系结构正在发生根本的转变。目前,业界致力于通过软件以这样一种方式来提供服务,即用户实际上可以在任何地方,在任何时间从任何设备与这些服务交互。人们已经将这种新模式与拨号音相提并论;几乎在世界上的任何角落,您都可以提起电话并马上听到拨号音。软件服务(例如:银行、保险、新闻以及旅游日程安排)很快就会像这个熟悉的嗡嗡声一样可以随处访问。IBM 的 WebSphere、HP 的 e-speak 和 Microsoft 的 Microsoft.Net 都是这种雄图大志的表现,Sun 的 Jini 也是。

服务的概念在 Jini 中处于核心地位。Jini 规范把服务定义为“......可以被人、程序或另一个服务使用的某种东西......服务在 Java 编程语言中将有计划地作为对象出现,也可能由几个其它对象组成......服务将有一个接口,它定义了可以向该服务请求的操作。服务的类型决定组成服务的接口。”(请参阅 参考资料查看 Jini 规范。)

在 Jini 中,软件组件通过代理使其服务可用。其它服务必须定位这个代理,以与 djinn(Jini 服务的联合体)内的服务交互。Jini 提供了一个健壮的机制帮助您查找代理及其它可选参数,这个机制建立在由代理实现的 Java 语言接口的基础上。然而,Jini 和其它面向服务的体系结构所承诺的无处不在的计算环境要求服务易于定位并且随时可用。

对于功能简单的服务,代理从查找(lookup)服务被检索到后可能就可以独立地运行 ― 使代理成为“严格本地”的代理。例如,一个把十进制数转换成等值的十六进制数的服务很可能可以完全在客户机的地址空间内执行这一计算。对于更多所涉及的服务,代理可能必须与在其它主机上运行的外部资源通信。一个示例是货币兑换器,它必须获得最新的汇率以执行其服务。对于这样一个服务,代理是不够的 ― 还必须拥有代理需要的任何其它资源。

Jini 环境中的 RMI

Jini 规范让代理、协议和服务器与服务实现通信。服务实现对客户机是隐藏的,要与服务交互,客户机只需遵循 Java 编程语言接口,而不是某种特定的有线协议。Java 远程方法调用(Remote Method Invocation(RMI))可以方便地安排这样的通信。Sun 对 Jini 规范的实现中的所有 Jini 服务都使用 RMI。由于 Jini 代理可以实现 J2SE 平台的任何部分,在 J2SE 平台中 RMI 是一个必需的组件,所以,使用 Jini 服务代理的任何客户机 VM 可能都支持 RMI。

RMI 允许一个对象使其方法调用对驻留在其它虚拟机,甚至其它主机上的对象可用。实现 java.rmi.Remote 的子类的对象可以将自己导出(export)到运行时,在特定端口上侦听传入的方法调用。 存根(stub),通常由 JDK 随带的 rmic 存根编译器(stub compiler)生成,释放 Remote 子类型中指定并从对象到对象进行实现的方法。一旦存根对客户机可用,客户机就可以调用存根的方法。接着,存根把方法调用转发到远程对象,并视需要对方法参数进行数据编入和数据编出。在这种情形中,作为 RMI 远程对象实现的 Jini 服务将向查找服务把它的存根注册为服务对象。图 1 说明了这个过程:

图 1:客户机从查找服务接收服务代理,开始与远程对象通信 客户机接收服务代理 

 

一个贷款审批服务

为了说明一个利用 RMI 的 Jini 服务,我构造了一个针对汽车购买人的贷款审批服务。这个服务收集关于客户和他想为之支付款项的汽车的信息;它还指明银行是否应批准该项贷款,如果批准,又是根据哪些条款。如果银行实现这个服务,那么它可能将这个服务作为其 Web 站点上的一个 applet,一个可以通过 ATM 接口进行使用的模块,一个可以通过无线 PDA 或蜂窝电话访问的组件,或者就是电话上的一个语音接口。

LoanService 接口只有一个方法:

      public interface LoanService       {             public LoanApproval requestLoan(LoanRequest request)                   throws RemoteException;      }

LoanRequest 对象封装关于客户和贷款的信息,例如:客户的年收入和信用报告、汽车的价格以及该项贷款所要求的条款。该方法返回一个 LoanApproval 对象,它提供银行对该项贷款的条款的详细信息,例如:年利率(APR)和所要求的保证金金额以及月还款额。如果银行不批准该项申请,则该方法返回空值。

这个服务接口不依赖于 RMI 或任何其它特定实现。它声明一个 java.rmi.RemoteException 是因为我们把这个服务预想为在分布式环境中运行。此外,考虑到网络和通信故障导致的异常, RemoteException 是一个有用的类。为了用 RMI 部署这个服务,我们需要引入一个继承 java.rmi.Remote 的层:

     public interface RemoteLoanService extends LoanService,             java.rmi.Remote       {      }

这个服务的现实实现很可能要利用专家系统来决定是否要开出一项贷款。为了让示例保持简单,我们将使用一个 Bank 对象模拟这项决策功能。属性文件存储批准一项贷款必须满足的条件。 Bank 对象的一个实例在服务启动时被初始化。给定一个 LoanRequestBank 对象将决定是否应以及在什么条款下接受某项贷款。图 2 说明了这个服务。

图 2:贷款审批服务 贷款审批服务

RemoteLoanService 作为一个 RMI 远程对象实现,因而必须接受来自其它虚拟机的方法调用。为了做到这一点,它将自己 导出到 RMI 运行时,RMI 运行时接着安排这个对象在特定端口上侦听传入的方法调用。 java.rmi.server.RemoteServer 类提供创建和导出对象的语义。在 JDK 1.1 所引入的 RMI 实现中,一个远程对象一旦将自己导出到 RMI 运行时,“只要创建该对象的 VM 正在运行”,这个远程对象就可以接受远程方法调用(即保持激活)。(请参阅 参考资料查看 RMI 规范。)

UnicastRemoteObjectRemoteServer 的子类 ― 简化了实现远程对象的这一办法。请遵照以下所要求的步骤实例化一个是 UnicastRemoteObject 的子类的服务:

  1. 通过调用对象的构造函数之一创建对象的一个实例
  2. 使用这个对象的引用并开发一个服务项
  3. 向 Jini 查找服务注册这个服务项

例如,我们的服务实现类可能看起来像这样:

public class LoanServiceImpl extends UnicastRemoteObject             implements RemoteLoanService {        //Object that makes the decision about the loan        Bank decisionSystem;        //simple constructor to initialize the directoryPath        public LoanServiceImpl(String path)                  throws RemoteException        {                  super();                                   //initialize bank object from the property file                   decisionSystem = initBank(path);        }        ....        //implementation of LoanService        public LoanApproval requestLoan(LoanRequest request)                  throws RemoteException         {                   //perform work here                   return decisionSystem.decide(request);        }        //load Bank object         private Bank initBank(String filePath)>        {                   //read property file from disk, initialize Bank object                   ....        }        ...

这个类的 main() 方法将创建这个对象的一个实例并将它向 Jini 查找服务注册:

   public static void main(String[] argv) throws Exception    {         //install an RMI security manager         if (System.getSecurityManager() == null)                    System.setSecurityManager(new RMISecurityManager());         //collect directory path for bank property file on command line         String path = argv[0];         //Create object. Note that we will need the remote interface,         //represented by the stub.         RemoteLoanService service =                   (RemoteLoanService) new LoanServiceImpl(path);      //Join Jini lookup service         JoinManager joinManager = new JoinManager(                  service,                   null,                   serviceIDListener,                   null,                   null);      }} 

一个熟练的开发者可能会声明每一个可能的异常;不过,为了简化这里提供的代码,我们将只抛出 Exception

JoinManager 类是一个 Jini 助手实用程序。我将不讨论使用这个类的语义,但请注意这些重要的内容:

  • 服务在启动时可能要求提供一些 初始化数据。一个示例就是银行的属性文件的目录路径,它由命令行指定。服务 ID 也是初始化数据;服务第一次加入到查找服务时,它接收到一个服务 ID。 ServiceIDListener 实现(它应把该 ID 保存到稳定的存储器)向对象发出通知。随后的服务注册必须重用这个 ID;例如,通过实现 JoinManager 的构造函数,它允许指定 ServiceID
  • 服务必须容纳一些连续的活动。这包括连续的发现及加入(discover and join)到查找服务、更新它在那些服务中的租用(lease),以及对事件进行侦听。 JoinManager 的实例将负责前两项任务。由于我们传了一个 null 实例代替 LookupDiscoveryManagement ,所以 JoinManager 将使用 LookupDiscoveryManager 的一个实例进行侦听,以发现来自查找服务的事件,这些事件是 public 组的成员。类似的,由于我们指定 null 来代替 LeaseRenewalManager ,所以 JoinManager 将创建并使用一个这样的对象来管理租用。

可以将 LoanServiceImpl 对象传到 Java 虚拟机(Java Virtual Machine(Java VM))执行。我们可以用命令行来运行我们的贷款服务,这个命令行指定并将属性传递到 Java VM,指定 LoanServiceImpl 类,并传递一个参数到 Java 程序本身。属性包含了运行 LoanServiceImpl 类所要求的 Java 安全策略文件,还包含了 RMI codebase,使用该服务的存根的客户机将从这个 RMI codebase 下载类:

  /usr/java1.3/bin/java /       -Djava.security.policy=/export/services/loanservice/loanservice.policy /      -Djava.rmi.server.codebase=http://objectserver:8080/ /      LoanServiceImpl /      /export/services/loanservice/bank.property

至此,客户机可以从查找服务定位贷款服务代理。当客户机调用 requestLoan() 方法时,存根将把这个方法调用转发到我们的 LoanServiceImpl ,接着 LoanServiceImpl 就将准备好处理贷款申请。

只要对象处于已导出状态(exported state),即它可以接受远程方法调用了,VM 就将为继承了 UnicastRemoteObject 的对象持续运行。由于 LoanServicerImplUnicastRemoteObject 的子类,所以 VM 将不会在运行了该类的 main 方法后返回,而是将继续运行,从而允许对象接受远程调用。

RMI 的设计师们预见到了这种办法的局限性:“分布式对象系统是设计来支持长期存在的持久对象的。假设这些系统将由数千个(可能是几百万个)这样的对象组成,那么,让对象实现成为激活的并保持激活是不合理的,这样会无限期占用宝贵的系统资源。此外,客户机需要有能力存储对对象的持久引用,以便可以在系统崩溃后重新建立对象之间的通信,因为在典型情况下,对分布式对象的引用仅在该对象是激活的时候才有效。”

上段文字来自 Jim Waldo、Ann Wollrath 和 Geoff Wyant 写的论文“Simple Activation for Distributed Objects”(请参阅 参考资料)。这篇论文还提供了这个问题的解决方案,并描述了一个在 Modula 3 Network Object 系统中的实现。后来,这个解决方案被应用到了 Java,发展成为 Java 激活体系结构(Java activation architecture)。

激活给我们带来了什么

在 RMI 术语中,“激活的对象是在某些系统的 Java VM 中实例化并导出了的远程对象。”(请参阅 参考资料查看 RMI 规范。)运行我们前面的 LoanServiceImpl 类的 main() 方法将产生一个激活的远程对象,因为它实例化并导出了 LoanServiceImpl 的一个实例。正如 RMI 规范所说,“被动(passive)对象是尚未在 Java VM 中实例化(或导出)的对象,但它可以被转换成激活状态。把被动对象转换成激活的对象就是一个称为激活的过程。”

对象激活的主要目标和首要好处是允许远程对象服务器管理它们的计算资源。根据 RMI 规范,“在 RMI 中,激活允许对象按照需要开始执行。当 可激活的远程对象被访问时(通过方法调用),如果该远程对象当前不正在执行,则系统在适当的 VM 中启动该对象的执行。”

使用 RMI 激活,系统也可以 取消激活一个对象,办法是使它成为 被动的并释放它的计算资源。有时 RMI 激活可以关闭运行该对象的 Java VM。当对该对象进行新的方法调用时,系统就启动该对象,把它转为 激活的,并且可能为该对象创建一个让对象在其中执行的 Java VM。

除了根据需要激活和取消激活对象之外,RMI 激活框架还长时间维护远程对象引用。当服务向查找服务注册它的代理时,代理就会包含一个对 RMI 远程对象实现的引用。如果该实现停止执行,则 RMI 存根 ― 在 Jini 服务对象中 ― 内的引用就变为无效。即使该远程对象重启,客户机也不能再使用该存根。该服务的唯一选择是向查找服务重新注册。

已经获得服务对象的客户机必须请求一份新副本 — 可能是在它们调用老的、无效存根的方法时,作为处理向它们抛出的 RemoteExceptions 的一部分。这种令人厌烦的方式削弱了 djinn,即使其系统管理和维护要求很简单。激活确保了服务对象中包含的引用在跨远程对象实现时保持有效。维护这样的持久对象引用是 RMI 激活框架的另一个重要的好处。

激活基础结构

开发者可以依靠 JDK 的健壮的基础结构来创建和维护高度可用的服务。激活允许我们向第三方系统组件注册对象,第三方系统组件将承担启动和停止服务的任务。一旦向这个组件注册了一个对象,该对象上的远程方法调用就将不会失败,即使那时候该对象正处于空闲。相反地,这个第三方组件将启动该对象的执行。

JDK 版本 1.2 及以上版本带有这样一个系统实用程序:可执行程序 rmid 。这个程序是 Sun 的对 激活系统的实现。(这个可执行文件实际上是类 sun.rmi.server.Activation 的一个包装器。)通常,一台主机运行一个激活器进程。激活系统执行两大任务:

  • 跟踪激活对象所需的信息
  • 管理对象在其中被激活的 Java VM

rmid 之外,JDK 还提供了一个 API,它允许程序员与激活系统进行交互,位于 java.rmi.activation 包。

代码 3:激活 API 的类激活 API 的类

在代码 3 中, Remote 接口是一个标记,它指明一个对象是 远程的。RMI API 提供一个抽象类 RemoteObject ,它实现 Remote 接口并提供 equals()hashCode()toString() 方法的正确的远程对象行为。远程引用语义由 RemoteObject 的称为 RemoteStub 的子类支持。RMI 系统中的所有客户机存根都是这个类的子代。

在服务器端,API 提供了抽象类 RemoteServer 。这个类抽象地提供了创建和导出远程对象的语义。这样,远程对象实现可以直接从 RemoteServer 派生子类,或者,更有可能的是,通过 RemoteServer 指定的子类之一 ― UnicastRemoteObjectActivatable 派生子类。前者是在 JDK 1.1 中引入的,提供不可激活的(nonactivatable)对象,而后者是自 JDK 版本 1.2 起出现的,方便了可激活的(activatable)远程对象的创建。但是,让单点广播(unicast)或可激活的远程对象继承其中之一并不是绝对必要的。Java 的单继承机制会阻止另一个类的子类型继承一个远程服务器类型。在那种情况下,程序员必须确保 equals()hashCode() 的正确的远程语义,然后用 UnicastRemoteObjectActivatableexportObject() 方法来使远程对象对 RMI 运行时可用。

使用这个 API 有两个不同的阶段:

  1. 对象向激活系统注册
  2. 激活系统触发该对象,以响应远程方法调用,然后用激活 API 创建该对象的一个实例

因此,在一个可激活对象的生命周期中,有两个不同的 VM: 设置 VM,它把对象向激活系统注册, 服务器 VM,它由 rmid 创建,用来运行激活了的对象。执行以下任务之后,设置 VM 就会存在:

  1. 给激活系统指定对象将在其中执行的 VM
  2. 给激活系统指定对象
  3. 向激活系统注册对象,藉此获得对象的激活 ID
  4. 把对象导出到 RMI 运行时

安装程序不必创建一个对象的实例来向激活系统注册该对象。当设置 VM 存在时,在任何 VM 中将不会有可激活的对象的实例运行。根据需求启动 VM 并安排它装入对象的实例是激活系统的职责。

 

激活贷款服务

一个对象要加入到激活进程必须满足两个要求:

  1. 它必须向激活系统(又称激活器(activator))注册一个 激活描述符(activation descriptor)
  2. 它必须提供一个特定的 激活构造函数(activation constructor)

一个对象一旦向激活系统注册了,它就为加入到 激活协议(activation protocol)做好了准备。

图 4:激活协议 激活协议

图 4 一步一步地说明了 RMI 规范中概述的激活协议。位于右边的 remote object 最初是非激活的。客户机从 Jini 查找服务获得一个服务对象,Jini 查找服务包含远程服务器的存根。接着,客户机试图在存根上进行一个方法调用。存根检查对远程对象的活引用(live reference)是否为空。如果是,则它包含的故障远程引用(faulting remote reference)就联系远程对象服务器上的激活系统。在注册过程中,激活系统用存根传递给它的激活 ID 来访问与对象相关联的激活描述符。

这个描述符允许激活系统检查预先存在的激活组(activation group)是否存在该对象。如果不存在,激活系统就将使用该组的描述符创建一个;这可能涉及为这个组派生一个新的 Java VM。激活系统把对象的创建工作委派给激活组,激活组用激活描述符创建对象,包括 MarshalledObject 包含的可能的引导数据。最后,这个组返回一个新的活对象引用给激活组,激活组把这个活引用传回给远程存根。激活组注意到对象现在处于激活状态,所以,随后对该对象的方法调用将只是返回这个活引用。

向激活系统注册 向激活系统注册的目的是提供足够的信息来激活或重新创建对象。提供关于对象的抽象的是 激活描述符 ActivationDesc 。它指定如下信息:

  • 对象的类名和代码位置作为字符串加以指定。
  • 对象引导自身所需的数据必须被封装进 java.rmi.MarshalledObjectMarshalledObject 的实例通过将任何可序列化的对象传递给它的构造函数来创建。数据编入的对象与完全序列化的对象不同,因为它包含指明可以从哪里检索该对象的类文件的位置信息;这个位置信息用 codebase URL 注释。当为创建 MarshalledObject 实例的 VM 设置 java.rmi.server.codebase 属性时,则该属性的值将被写入 MarshalledObject 。例如,贷款服务就需要初始化银行属性文件在文件系统上的位置。
  • 对象的重新启动模式指定是否激活系统一出现就应使对象运转( 急切激活(eager activation)),或者是根据需求,当对象接收到第一个远程方法时才使对象运转( 延迟激活(lazy activation))。缺省值是延迟激活。

ActivationDesc 还指定对象的激活组。激活协议使用激活组描述来确定在哪个 VM 中将对象激活。

根据需求的 VM

RMI 激活规范陈述说“系统在适当的 VM 内启动对象的执行。”对这个 VM 的抽象是激活组或 ActivationGroup ;同一个激活组中的对象在同一个 VM 中激活。当创建了一个新的激活组时,在主机上运行的激活器进程将产生一个新的 VM。这个 VM 将作为子进程运行,从而允许激活器发出通知,例如通知 VM 是不是崩溃了。当对属于这个 VM 的对象的下一个方法调用到来时,激活器将重新启动这个 VM。请记住我们用来启动我们的原始服务的命令行:

 /usr/java1.3/bin/java /     -Djava.security.policy=/export/services/loanservice/loanservice.policy />      -Djava.rmi.server.codebase=http://objectserver:8080/ /      LoanServiceImpl /      /export/services/loanservice/bank.property

激活系统必须给它所产生的任何新的 VM 指定类似的信息。 ActivationGroupDesc 类提供了指定这些信息的一种办法。这个类有很大的灵活性:您可以告诉激活系统为每个组启动一个您选择的虚拟机,并给每个 VM 传递不同的属性,例如安全策略。

ActivationGroupDesc 允许我们指定 VM 属性,这些属性通常用 java.util.Property 对象通过 -D 命令行选项进行传递:

     Properties vmProperties = new Properties();      vmProperties.put("java.security.policy",            "/export/services/loanservice/loanservice.policy");      vmProperties.put("java.rmi.server.codebase",             "http://objectserver:8080/");

ActivationGroupDesc.CommandEnvironment 还允许我们指定组的 Java VM 需要的命令环境。例如,我们可以指定 Java VM 和命令参数的可执行文件(而不是属性):

      ActivationGroupDesc.CommandEnvironment commandEnv =             new ActivationGroupDesc.CommandEnvironment(                  "/usr/java1.3/bin/java",                   new String[] {"-Xmx48"});

现在,我们已经准备好向激活系统注册我们的组:

     ActivationGroupDesc groupDesc =             new ActivationGroupDesc(vmProperties, commandEnv);      ActivationGroupID groupID =             ActivationGroup.getSystem().registerGroup(groupDesc);

注册会返回 ActivationGroupID ,它向激活系统唯一地标识了这个组。要在同一个 VM 内运行多个 Jini 服务,我们可以将组的 ID 保存到磁盘,以使其它可激活的对象可以在注册时使用它。以下显示贷款服务实现类的新的 main() 方法:

  public static void main(String[] argv) throws Exception   {    //collect directory path for bank property file on command line    String path = argv[0];       //specify group properties       Properties vmProperties = new Properties();       vmProperties.put("java.security.policy",            "/export/services/loanservice/loanservice.policy");       vmProperties.put("java.rmi.server.codebase",             "http://objectserver:8080/");       //use default command environment        ActivationGroupDesc groupDesc =             new ActivationGroupDesc(vmProperties, null);       ActivationGroupID groupID =             ActivationGroup.getSystem().registerGroup(groupDesc);       //class name and location for object       String className = "LoanServiceImpl";       String codeLocation = "http://objectserver:8080/";       //bootstrap data for object       MarshalledObject data =             new MarshalledObject("/export/services/loanservice/bank.property");       //this constructor will also create the group       ActivationDesc activationDesc =             new ActivationDesc(groupID, className, codeLocation, data);       RemoteLoanService service =              (RemoteLoanService)Activatable.register(activationDesc);   //Join Jini lookup service. Same as before.      //We will change this later to delegate lease management,       //lookup discovery, and event handling.      JoinManager joinManager = new JoinManager(            service,             null,             serviceIDListener,             null,             null);      }  } 

正如我前面提到过的,激活系统的职责之一是充当激活信息的数据库。给定一个激活 ID,激活系统就可以确定该对象是否处于激活状态;如果不是,它可以从该对象的激活描述检索足够的信息来重新创建该对象。激活系统把这个注册信息保存到稳定的存储器。

rmid 的日志目录提供了这种持久性。当 rmid 启动时,它从日志文件重新构造这些映射。这样,当激活系统重新启动时,可激活的对象就不必重新注册。先前向 rmid 注册了的任何服务在 rmid 重新启动后对激活仍然可用。因为 Jini Starter Kit 中的许多 Jini 服务都是作为可激活的对象提供的,所以您不必手工重新启动这些服务。例如,Jini 查找服务,一旦向 rmid 注册了,就将在机器重新引导后 rmid 恢复运行时自动重新启动。因此,删除 rmid 的日志目录是不明智的,尤其是在生产系统中,因为会破坏成百上千个对象的注册。因此,这些对象不能处理远程方法调用,除非显式地向激活系统注册 ― 例如,通过运行它们的安装程序。

用诸如这样的命令行指定 rmid 的日志目录:

 

      rmid -log LOG_DIR

此外,应像如下所示关闭 rmid 以允许将它保存为持久状态:

 

      rmid -stop

激活构造函数 可激活的对象必须提供一个特殊的构造函数以协助激活。激活系统将调用这个构造函数来实例化该对象。可激活的对象最易于实现为 java.rmi.activation.Activatable (另一种 RemoteServer 类型)的子类。 Activatable 的构造函数之一还管理对象的注册和导出。这样,我们将从 Activatable 派生 activatable LoanServiceImpl 子类,并给它配备一个激活构造函数:

public class LoanServiceImpl extends java.rmi.activation.Activatable      implements RemoteLoanService {  //Object that makes the decision about the loan  Bank decisionSystem;  //activation constructor  public LoanServiceImpl(ActivationID id, MarshalledObject data)            throws RemoteException   {        super(id, 0);        String path = (String)data.get();        decisionSystem = initBank(path);  }        ....  //implementation of LoanService  public LoanApproval requestLoan(LoanRequest request)             throws RemoteException   {             //perform work here             return decisionSystem.decide(request);  }  //load Bank object   private Bank initBank(String filePath)  {             //read property file from disk, initialize Bank object             ....  }  ...

持久引用

除了管理对象服务器上的计算资源之外,激活还提供远程对象引用的长期维护。当客户机生成对远程服务实现的方法调用时,它假设存根中包含的引用是有效的;如果无效,则方法调用将产生 RemoteException 。如果没有激活,客户机应用程序就会重试这一操作(希望操作会成功),从查找服务重新获得服务代理,或者只是通知用户服务器连接终止了。

有了可激活的服务实现,存根将尝试通过活远程引用联系对象,就和有不可激活的实现时所做的一样。如果该引用无效,存根将使用另一个引用,这次是对在远程对象的主机上运行的激活系统的引用。这个激活系统作为内部 RMI 引用存储在存根中。如果存根接收到要求激活一个对象的请求,它将返回一个对远程对象的活引用给调用者 ― 对于我们的情况,是 Jini 客户机。这样,这第二个远程调用将返回一个有效的服务引用给我们的 applet。这称为 故障远程引用,因为它允许激活系统按照需求对对象“进行故障引用”。这个过程对客户机是透明的,仅仅是服务的实现问题。

以下情形说明了维护持久引用的好处:

  1. 启动可激活的服务。它将向 rmid 和 Jini 查找服务注册。
  2. 启动客户机。它将从查找服务获得服务对象,并将使用这个对象对服务实现进行远程方法调用。
  3. 假设系统管理员重新引导运行着远程服务实现的机器,破坏了该进程中的远程对象及其 Java VM。如果客户机试图生成对该服务器的方法调用,它将接收到一个 RemoteException ,这意味着该服务不可用。几分钟后,远程对象服务器恢复运行。由于系统管理员把 rmid 配置成当机器恢复运行时启动(例如,通过 Unix init 机制,或者作为一个 NT 服务),所以, rmid 启动,读取它的日志文件,并重新初始化它的状态。然后,如果客户机(它在服务器重新引导期间一直在运行)对该服务器进行一个远程方法调用,它将发现没有对远程对象的活引用。接着,它将询问服务器的激活系统,这个激活系统将激活该对象并向客户机返回一个对该对象的活引用。假设该对象可以被激活,则方法调用就将成功。

 

取消激活服务

我从激活的最大好处是它对长期运行的对象服务器中的系统资源的管理谈起。我们还了解了激活系统如何根据需求执行对象。然而,激活系统不会自动取消激活对象。

没有一种标准的方式来衡度对象是被“重度”使用了还是“轻度”使用了;这取决于具体情形。此外,即使对象极少被使用,但它们在其中运行的服务器的负载很轻,则取消激活这些对象将没有什么意义。这些是程序员或系统管理员要做的决定。例如,服务可能会有一个正在运行的单独的线程来衡度使用程度。当这个线程确定对象处于空闲的时候,它就调用 Activatable.inactive(activationID) 通知该对象的激活组。如果没有暂挂的对该对象的方法调用,则激活组将 取消导出(unexport)该对象, inactive() 将返回 true 。如果有暂挂的调用,则方法不会被取消导出。如果该对象是正在激活组中运行的唯一对象,则激活系统会同时关闭该组的 VM。

如果我们要让一个服务中止它对激活系统的可用性,我们可以用以下调用 取消注册它:

 

      Activatable.unregister(activationID)

不同于非激活的对象,取消注册了的对象将不会在它接收到远程方法调用时激活。服务的管理功能应允许管理员在关闭服务时注销对象。

回页首

当您睡着时......

既然我们的贷款服务是可激活的,那么,能够管理它的资源,能够把它的启动和关闭委派给 rmid ,又能够在对象的实例化间维持持久引用还会更好一些。然而,激活带来了我们必须处理的一组新问题。

回想一下,向激活系统注册可激活的服务会返回一个远程存根给服务,而不启动对象的一个实例。我们用这个存根来向查找服务注册可激活的服务。可激活的服务将需要更新它的租用,即使是在非激活期间。良好的 Jini 表现还要求可激活的服务发现并加入到新的查找服务。最后,可激活的服务在它非激活期间会对事件感兴趣。如果被激活的服务只用来处理这些职责,那么将会是一种资源浪费。这些职责应委派给第三方服务。Jini 助手实用程序(Jini Helper Utility)及服务规范(Service Specification)引入了租用更新服务(lease renewal service)、查找发现服务(lookup discovery service)以及事件邮箱服务(event mailbox service)。

 

参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
  • David Gelernter 在他的宣言 The Second Coming: http://www.edge.org/3rd_culture/gelernter/gelernter_index.html中以雄辩的语言描述了软件服务在网络上透明可用的未来展望。
  • Sun 的 Web 站点有 Jim Waldo、Ann Wollrath 和 Geoff Wyantsite 写的 Sun 技术报告 TR-95-46,“Simple Activation for Distributed Objects”: http://www.sun.com/research/techrep/1995/abstract-46.html
  • RMI 规范: http://java.sun.com/j2se/1.3/docs/guide/rmi/spec/rmiTOC.html
  • RMI 规范的第 7 章具体描述远程对象激活(remote object activation): http://java.sun.com/j2se/1.3/docs/guide/rmi/spec/rmi-activation.html
  • Sun 的 RMI 主页有许多有用的关于 RMI 的方方面面的指示: http://java.sun.com/products/jdk/rmi
  • Sun 的 RMI 小组提供了一篇很有用的激活教程: http://java.sun.com/j2se/1.3/docs/guide/rmi/activation.html
  • Jim Waldo ― Jini 的首席设计师,在其“The End of Protocols”(java.sun.com,2000 年 6 月)中描述了协议的终结并解释了为什么 Jini 不依赖于 RMI(要求免费注册到 Java Developer Connection): http://developer.java.sun.com/developer/technicalArticles/jini/protocols.html
  • Samuel C. Kendall、Jim Waldo、Ann Wollrath 和 Geoff Wyant 所写的 Sun 技术报告 TR-94-29,“A Note on Distributed Computing”,描述了 RemoteException 的角色,解释了为什么它是检查型异常而不是运行时异常,并讨论了远程和本地方法调用的差异: http://www.sun.com/research/techrep/1994/abstract-29.html
  • Ann Wollrath 在张贴到 RMI 用户列表的一条消息中解释了实现持久远程引用的不同途径。她还讨论了如何使用 ActivationGroupDesc 来为组建立一个公共环境: http://archives.java.sun.com/cgi-bin/wa?A2=ind0007&L=rmi-users&P=R13480
  • Jini 主页包含有关于所有 Jini 技术文档的指示: http://www.jini.org
  • Jini 规范: http://www.sun.com/jini/specs
  • Bill Venners 维护 Jini FAQ: http://www.artima.com/jini/faq.html
  • Venners 还在他的 Web 站点上管理 Jini Corner: http://www.artima.com/jini/index.html
  • Sun 的 John McClain 向 Jini 用户列表张贴了“The Process Architecture of the JSK and JSTK”。它提供了对 RMI 激活在 JSK 所带服务的 Sun 的实现中的角色的易懂的解释:
    • 第 I 部分: http://archives.java.sun.com/cgi-bin/wa?A2=ind9903&L=jini-users&P=R14099
    • 第 II 部分: http://archives.java.sun.com/cgi-bin/wa?A2=ind9903&L=jini-users&P=R36142
  • 面向服务的计算前景的证明
    • IBM 的 WebSphere: http://www-4.ibm.com/software/info/websphere/index.html
    • Microsoft 的 Microsoft .Net: http://www.microsoft.com/net
  • 经 JavaWorld杂志许可转发。IDG.net(一家 IDG Communications 公司)版权所有。注册获取免费的 JavaWorld 电子邮件时事通讯。

 

关于作者

 

Frank Sommers 是 Autospaces的创立者和 CEO,Autospaces 是一家将 Jini 技术投到汽车软件市场的创业公司。自 1996 年以来,他一直在 Nowcom Corporation 担任技术副总裁,这是一家为金融业和保险业提供基础设施的公司。在参加了 1995 年 11 月在 Sun Microsystems 园区举行的关于 Java 语言的第一次公开演示之后,他就一直在用 Java 编程。他的兴趣包括并行和分布式计算、数据库知识的发现和表述以及计算的哲学基础。当不考虑电脑方面的问题的时候,他编写钢琴曲、弹钢琴,琢磨 Gustav Mahler 的交响曲,探究 Aristotle 和 Ayn Rand 的著作。Sommers 想感谢 Sun Microsystems 的 John McClain,感谢他为本文提出的宝贵意见。您可以通过 frank.sommers@javaworld.com 与 Frank Sommers 联系。

原创粉丝点击