|
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的行为是隐含进行的,所以跳过不谈。我们将列出方法名和所需的接口。在每个阶段中,会有一些由方法名标识的步骤。如果组件扩展了括号中指定的接口,这些步骤会依次执行。
初始化阶段 |
以下的步骤依次发生,在组件生存期中只发生一次。
-
enableLogging()
[LogEnabled ]
-
contextualize()
[Contextualizable ]
-
compose()
[Composable ]
-
configure()
[Configurable ] or
parameterize()
[Parameterizable ]
-
initialize()
[Initializable ]
-
start()
[Startable ]
|
活动服务阶段 |
以下的步骤依次发生,但在组件的生存期中可能发生多次。请注意,如果您选择不实现Suspendable接口,那么您的组件有责任在执行任何re开头的步骤时保证正确的功能。
-
suspend()
[Suspendable ]
-
recontextualize()
[Recontextualizable ]
-
recompose()
[Recomposable ]
-
reconfigure()
[Reconfigurable ]
-
resume()
[Suspendable ]
|
销毁阶段 |
以下的步骤依次发生,在组件生存期中只发生一次。
-
stop()
[Startable ]
-
dispose()
[Disposable ]
|
|
Avalon Framework契约 |
在本部分中,我们将按字母次序介绍所有内容,除了最重要的部分:Component,我们把它放在最前面。
当我使用"容器"或"容纳"来描述组件时,我是有特殊含义的。我是指那些已经由父组件实例化并控制的子组件。我不是指通过ComponentManager或ComponentSelector得到的组件。更进一步,容器组件所接收到的一些Avalon步骤执行命令必须传播到它的所有子组件,只要这些子组件实现了相应的接口。特定的接口是指Initializable、Startable、Suspendable和Disposable。
这样安排契约的原因是这些接口有特别的执行约定。
Component |
这是Avalon
Framework的核心。这个考虑方向所定义的接口会抛出ComponentException异常。
Component |
每个Avalon组件必须
实现Component接口。Component Manager和Component
Selector只处理Component。这个接口没有定义方法。它只是作为一个标记性接口。
任何组件必须使用不带参数的缺省构造方法。所有配置都是通过Configurable 或Parameterizable 接口来完成的。 |
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开头的那些接口的学习难度。Reconfigurable
对Configurable
来说就象Recomposable
对Composable 一样。 |
|
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开头的接口的学习难度。Recontextualizable
对Contextualizable
就如同Recomposable
对Composable 。 |
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 对象,测试合适的组件或最低版本号也会更容易。 |
|
|
|
|