Jakarta main

Avalon main

About


章节


原文链接


Printer Friendly


中文译者


框架和基础

我们将描述Avalon的契约和接口,为我们实际编写组件打下基础。

Avalon Framework是整个Avalon项目的中心部分。如果您理解了框架所定义的契约和结构,您就能理解任何利用该框架的代码。请记住我们已讨论过的原理和模式。在本部分中,我们将详细解释角色的概念在实践中是怎样起作用的,组件的生命周期以及接口是如何工作的。


定义组件的角色

在Avalon中,所有的组件都扮演一个角色。原因是您通过角色来获取您的组件。在这个舞台上,我们唯一要考虑的是角色的签名。回顾一下第二部分,我们把组件定义为"一个工作接口和该工作接口的实现的组合"。工作接口就是角色。

创建角色的接口

下面您将看到一个接口的例子,以及一些最佳的实践和原因。

package org.apache.bizserver.docs;

public interface DocumentRepository extends Component
{
    String ROLE = DocumentRepository.class.getName();

    Document getDocument(Principal requestor, int refId);
}

      
最佳实践

  • 包含一个名为"ROLE"的字符串,这是角色的正式名字。该名字与该工作接口的完全限定名称是一样的。这在今后我们需要得到一个组件的实例时会有帮助。

  • 如果有可能,请扩展组件接口。这会使您在发布组件时变得更容易。如果您不负责控制该工作接口,那么这点对你无用。问题也不会太大,因为您在发布时总可以将其强制转换为Component 的实例。

  • 做一件事并把它做好。组件的接口应该尽可能地简单。如果您的工作接口扩展了其它一些接口,就会把组件的契约给搞得难以理解。一个老的美国首字母缩写对这一点表述得很好:Keep It Simple, Stupid (KISS)。比自己更聪明(犯傻)并不难,我自己就干过几次。

  • 只确定您需要的方法。客户程序应该不知道任何实现细节,太多的可替换方法只会带来不必要的复杂性。换言之,选择一种方式并坚持不变。

  • 别让您的角色接口扩展任何生命周期或生存方式的接口。如果实现任何一个这样的类或接口,您就是在试图实现规范。这不是一个好模式,只会在将来带来调试和实现问题。


选择角色名称

在Avalon中,每个角色都有一个名称。它是您取得系统中其它组件引用的方式。Avalon开发团队已经概括了一些对角色命名的习惯方式。

命名习惯方式

  • 工作接口的完整限定名通常就是角色名。例外情况列在本通用规则的下面。在这个例子里,我们理论上的组件名称应该是"org.apache.bizserver.docs.DocumentRepository"。这就是应该包含在您的接口的"ROLE"属性里的名字。

  • 如果我们通过一个组件选择器得到了该组件的引用,我们通常使用从第一条规则推导出的角色名称,在末尾加上单词"Selector"。这条命名规则的结果将是"org.apache.bizserver.docs.DocumentRepositorySelector"。您可以通过DocumentRepository.ROLE + "Selector"来得到这个名称。

  • 如果我们有多个组件实现相同的工作接口,但用于不同目的,我们将分离角色。一个角色是组件在一个系统中的目的。每个角色名都将以最初的角色名开头,但表示角色目的的名字会以/${purpose}的形式附在后面。例如,对DocumentRePository我们可以有如下的目的: PurchaseOrder(购买订单)和Bill(账单)。这两个角色可被分别表述为DocumentRepository.ROLE + "/PurchaseOrder"DocuementRepository.ROLE + "/Bill"





Framework接口概述

整个Avalon Framework可以被分成七个主要类别(根据API): Activity, Component, Configuration, Context, Logger, Parameters, Thread, and Miscellany。每一类(Miscellany除外)表示了一个考虑方向(concern area)。一个组件通常实现几个接口来标明它所关心的考虑方向。这使组件的容器能以一致的方式来管理每个组件。

Avalon接口的生命周期

当一个框架实现了多个接口以分开考虑组件的各个方面时,存在搞不清方法调用次序的潜在可能性。Avalon Framework意识到了这一点,因此我们开发了事件生命周期次序的协定。如果您的组件不实现相关的接口,就简单跳到下一个要处理的事件。因为存在一个正确的创建和准备组件的方法,您可以在接收到事件时设置好组件。

组件的生命周期被分成三个阶段:初始化阶段、活动服务阶段和销毁阶段。因为这些阶段是依次发生的,我们将依次讨论这些事件。另个,因为Java语言的原因,Construction和Finalization的行为是隐含进行的,所以跳过不谈。我们将列出方法名和所需的接口。在每个阶段中,会有一些由方法名标识的步骤。如果组件扩展了括号中指定的接口,这些步骤会依次执行。

初始化阶段

以下的步骤依次发生,在组件生存期中只发生一次。

  1. enableLogging() [LogEnabled]

  2. contextualize() [Contextualizable]

  3. compose() [Composable]

  4. configure() [Configurable] or parameterize() [Parameterizable]

  5. initialize() [Initializable]

  6. start() [Startable]


活动服务阶段

以下的步骤依次发生,但在组件的生存期中可能发生多次。请注意,如果您选择不实现Suspendable接口,那么您的组件有责任在执行任何re开头的步骤时保证正确的功能。

  1. suspend() [Suspendable]

  2. recontextualize() [Recontextualizable]

  3. recompose() [Recomposable]

  4. reconfigure() [Reconfigurable]

  5. resume() [Suspendable]


销毁阶段

以下的步骤依次发生,在组件生存期中只发生一次。

  1. stop() [Startable]

  2. dispose() [Disposable]



Avalon Framework契约

在本部分中,我们将按字母次序介绍所有内容,除了最重要的部分:Component,我们把它放在最前面。

当我使用"容器"或"容纳"来描述组件时,我是有特殊含义的。我是指那些已经由父组件实例化并控制的子组件。我不是指通过ComponentManager或ComponentSelector得到的组件。更进一步,容器组件所接收到的一些Avalon步骤执行命令必须传播到它的所有子组件,只要这些子组件实现了相应的接口。特定的接口是指Initializable、Startable、Suspendable和Disposable。 这样安排契约的原因是这些接口有特别的执行约定。

Component

这是Avalon Framework的核心。这个考虑方向所定义的接口会抛出ComponentException异常。

Component

每个Avalon组件必须 实现Component接口。Component Manager和Component Selector只处理Component。这个接口没有定义方法。它只是作为一个标记性接口。

任何组件必须使用不带参数的缺省构造方法。所有配置都是通过ConfigurableParameterizable接口来完成的。


Composable

一个用到其它组件的组件需要实现这个接口。这个接口只有一个方法compose(),该方法带唯一一个ComponentManager 类型的参数。

围绕该接口的契约是:在组件的生存期中,compose()被调用一次且仅被调用一次。

这个接口与其它任何定义了方法的接口一样,都使用的是反向控制模式。它由组件的容器调用,只有该组件需要的那些组件会出现在ComponentManager中。


Recomposable

在少数的情况下,组件会需要一个新的ComponentManager和新的组件-角色映射关系。在这些情况下,需要实现recomposable接口。它的方法也与Composable的方法名称不一样,是recompose()

围绕该接口的契约是:recompose() 方法可以被调用任意多次,但不能在组件完全初始化好之前调用。当该方法被调用时,组件必须以一种安全和一致的方式更新它自己。通常这意味着组件进行的所有操作必需在更新之间停下来,在更新之后再继续。



Activity

这组接口与组件生命周期的契约相关。如果在这组接口调用过程中出现了错误,您可以抛出一个通用的Exception。

Disposable

如果一个组件需要以一种结构化的方式知道自己不在需要了,它可以使用Disposable接口。一旦一个组件被释放掉,它就不能再用了。实际上,它就等待着被垃圾收集。该接口只有一个方法dispose(),该方法没有参数。

围绕该接口的契约是:dispose()方法被调用一次,也是组件生存期中调用的最后一个方法。同时也表明该组件将不再使用,必须释放该组件所占用的资源。


Initializable

任何组件如果需要创建其它组件,或需要执行初始化操作从其它的初始化步骤中获取信息,就要用到Initializable接口。该接口只有一个initialize()方法,该方法没有参数。

围绕该接口的契约是:initialize()方法被调用一次,它是初始化过程中最后被调用的方法。同时也表明,该组件已处于活动状态,可以被系统中其它组件使用。


Startable

任何组件如果在其生存期中持续运行,就要用到Startable 接口。该接口定义了两个方法:start()stop()。这两个方法都没有参数。

围绕该接口的契约是:start()方法在组件完全初始化之后被调用一次。stop() 方法在组件被销毁之前被调用一次。它们都不会被调用两次。start() 总在stop()之前被调用。对该接口的实现要求安全地执行start()stop() 方法 (不像Thread.stop()方法) 并且不会导致系统不稳定。


Suspendable

任何组件如果在其生命期中允许自己被挂起,就要用到Suspendable 接口。虽然它通常总是和Startable 接口在一起使用,但这不是必需的。该接口有两个方法:suspend()resume()。这两个方法都没有参数。

围绕该接口的契约是:suspend() and resume() 可以被调用任意多次,但在组件初始化并启动之前,或在组件停止并销毁之后不能调用。 对已挂起的组件调用suspend() 方法,或对已在运行的组件调用resume() 将没有任何效果。



Configuration

这一组接口描述了配置方面的考虑。如果发生任何问题,例如没有所需的Configuration 元素,那么可以抛出ConfigurationException异常。

Configurable

那些需要根据配置确定其行为的组件必须实现这个接口以得到Configuration 对象的实例。该接口有一个configure() 方法,只有一个Configuration 类型的参数。

围绕该接口的契约是:configure() 方法在组件的生存期中被调用一次。传入的Configuration 对象一定不能为null


Configuration

Configuration对象是一由配置元素组成的树,配置元素拥有一些属性。在某种程度上,您可以将配置对象看作一个大大简化的DOM。Configuration类的方法太多,不便在本文中介绍,请看JavaDoc文档。您可以从Configuration 对象中取得String, int, long, float, or boolean类型的值,如果配置没有将提供缺省值。对属性也是一样的。您也可以取得子Configuration 对象。

契约规定,具有值的Configuration 对象不应有任何子对象,对子对象也是这样的。

你会注意到你无法得到父Configuration 对象。设计就是这样做的。为了减少配置系统的复杂性,大多数情况下容器会将子配置对象传给子组件。子组件不应该有权访问父配置的值。这种方式可能带来一些不便,但是Avalon团队在需要做折衷的情况下总是选择把安全性放在第一。


Reconfigurable

实现该接口的组件行为与Recomposable 组件非常相似。它只有一个reconfigure()方法。这样的设计决策是为了降低Re开头的那些接口的学习难度。ReconfigurableConfigurable 来说就象RecomposableComposable一样。



Context

Avalon中Context 的概念起源于需要一种机制来实现从容器向组件传递简单对象。确切的协议和名字绑定有意没有定义,以便给开者提供最大的灵活性。围绕Context 对象的使用的契约由您在您的系统中定义,尽管机制是一样的。

Context

Context接口只定义了一个get()方法。它有一个Object 类型的参数,返回以参数对象为键值的一个对象。Context 对象由容器组装,然后传递给子组件,子组件对Context只有读权限。

除了Context 对子组件总是只读的之外,没有别的契约。如果您扩展了Avalon的Context,请注意遵守该契约。它是反向控制模式的一部分,也是安全性设计的一部分。另外,在Contex中传一个引用 给容器的做法也是不好的,原因和Context应该是只读的相同。


Contextualizable

希望接收来自于容器的Context对象的组件应该实现该接口。它有一个名为contextualize() 的方法,参数是容器组装的Context 对象。

围绕这个接口的契约是:contextualize() 在组件的生存期中被调用一次,在LogEnabled 之后,但在其它初始化方法之前。


Recontextualizable

实现该接口的组件行为与Recomposable 组件非常相似。它只有一个名为recontextualize()的方法。这样的设计决策是为了降低Re开头的接口的学习难度。RecontextualizableContextualizable 就如同RecomposableComposable


Resolvable

Resolvable接口用于标识一些对象,这些对象在某些特定上下文中需要分离(need to be resolved)。一个例子是:某对象被多个Context 对象共享,并根据特定的Context改变其自身的行为。在对象被返回之前Context会调用 resolve() 方法。



Logger

每个系统都需要具备对事件记录日志的能力。Avalon内部使用了它的LogKit项目。尽管LogKit有一些方法可以静态地访问一个Logger实例,但Framework希望使用反向控制模式。

LogEnabled

每个需要Logger实例的组件都要实现该接口。该接口有一个名为enableLogging() 的方法,将Avalon Framework的Logger 实例传递给组件。

围绕该接口的契约是:在组件的生存期中只调用一次,在任何其它初始化步骤之前。


Logger

Logger接口用于对不同的日志库进行抽象。它提供一个唯一的客户端API。Avalon Framework提供了三个实现该接口的封装类:针对LogKit的LogKitLogger 、针对Log4J的Log4jLogger 、和针对JDK1.4日志机制的Jdk14Logger



Parameters

Avalon认识到Configuration对象层次结构在许多场合下太重量级了。因此,我们提出了一个Parameters对象来提供Configuration 对象的替代,使用名称-值对的方法。

Parameterizable

任何希望用Parameters 来取代Configuration 对象的组件将实现该接口。Parameterizable 只有一个名为parameterize()的方法,with the parameter being the Parameters object.

围绕该接口的契约是:它在组件的生存期中被调用一次。该接口与Configurable接口不兼容。


Parameters

Parameters对象提供了一种通过一个String 类型的名字取得值的机制。如果值不存在,有方便的方法允许你使用缺省值,也可以得到Configurable 接口中任何相同格式的值。

尽管Parameters 对象与java.util.Property 对象之间存在相似性,但它们还是存在重要的语义差别。首先,Parameters只读的。 其次,Parameters 总是很容易从Configuration 对象中导出。最后,Parameters 对象是从XML 片断中导出的,看上去是这样的:

<parameter name="param-name" value="param-value"/>

          


Thread

线程标记器(marker)用于根据组件的使用发出容器的基本语义信息信号。它们考虑到线程安全,对组件的实现提供标记。最佳实践是把这些接口的实现推迟到最终实现该组件的类。这样做避免了一些复杂情况,有时某个组件被标记为ThreadSafe,但从它派生出来的组件实现去不是线程安全的。这个包中定义的接口组成了我们称之为LifeStyle系列接口的一部分。 另一个LifeStyle接口是Excalibur包的一部分(所以它是这个核心接口集的扩展),Poolable定义在Excalibur的池实现中。

SingleThreaded

围绕SingleThreaded组件的契约是实现该接口的组件不允许被多个线程同时访问。每个线程需要有该组件的自己的实例。另一种做法是使用一个组件池,而不是每次请求该组件时都创建一个新的实例。为了使用池,您需要实现Avalon Excalibur的Poolable接口,而不是这个接口。


ThreadSafe

围绕ThreadSafe组件的契约是:不管有多少线程同时访问该组件,它们的接口和实现都能够正常工作。尽管这是一个有弹性的设计目标,但有时候由您所使用的技术,它就是不能实现。实现了这个接口的组件通常在系统中只有一个实例,其它的组件将使用该实例。



其它

在Avalon Framework的根包(root package)中的这些类和接口包括了Exception层次和一些通用工具类。但是有一个类值得一提。

Version

JavaTM 版本技术是在jar包中的manifest文件中有一项指定。问题是,当jar被解包后您就失去了版本信息,并且版本信息放在易于修改的文本文件中。当您把这些问题与一个较陡的学习曲线放在一起考虑时,检查组件和接口的版本就比较困难。

Avalon开发小组设计了Version对象,让您可以容易地检查版本和比较版本。您可以在您的组件中实现Version对象,测试合适的组件或最低版本号也会更容易。







Copyright ?999-2002 by the Apache Software Foundation. All Rights Reserved.