首页 > 代码库 > Avalon Framework概念

Avalon Framework概念

转述参考自:http://www.cjsdn.net/post/print?bid=29&id=59107

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。这个接口没有定义方法。它只是作为一个标记性接口。任何组件必须使用不带参数的缺省构造方法。所有配置都是通过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="http://www.mamicode.com/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对象,测试合适的组件或最低版本号也会更容易。

实现梦想

我们将向你展示怎样使用Avalon Framework和Avalon Excalibur来实现您的服务应用程序。我们将向您展示Avalon有多么易于使用。
在您完成分析以后,您需要创建组成您的系统的组件与服务。如果Avalon只是描述了一些您可以使用的编程习惯,那么它的用处就不大。但即使是这样,运用这些编程习惯和模式也会对理解整个系统有所帮助。Avalon Excalibur提供了一些有用的组件和工具,您可以在您自己的系统中使用它们,这可以让您的日子过得更轻松些。作为我们的演示,我们把定义一个组件从一个repository中取出一个文档实现的全过程走一遍。如果您还记得我们关于理论上的业务服务器的讨论,我们曾确定将这个组件作为一个服务。在实际情况中,一个组件就是一个服务的情况是很多的。

实现该组件
这里,我们定义如何实现我们的组件。我们会把实现前面提到的DocumentRepository组件的过程整个走一遍。我们需要弄清楚的第一件事就是我们的组件所关注的领域。然后我们需要弄清楚怎样创建和管理我们的组件。选择关注的领域
我们在前面已经为DocumentRepository组件定义了角色和接口,我们已准备好来创建实现。因为DocumentRepository的接口只定义了一个方法,我们有机会创建一个线程安全的组件。这是最受欢迎的一类组件,因为它允许只消耗最少的资源。为了让我们的实现是线程安全的,我们确实需要仔细考虑如何实现该组件。既然我们所有的文档都存放在数据库中,而且我们希望使用一个外部的Guardian 组件,我们将需要访问其它组件。作为一个负责任的开发者,我们希望对有助于调试组件的信息记录日志,追踪内部发生了什么。Avalon框架的优美之处在于,您只需实现您需要的接口,可以忽略不需要的那些。这就是Separation of Concerns带来的好处。当您发现需要考虑一个新的方面时,您只需实现相关的接口就为组件加入了新的功能。 对于使用您的组件的部分来说,不需要作任何改动。既然线程安全是一个设计目标,我们就已经知道了需要实现ThreadSafe接口。DocumentRepository接口只有一个方法,所以对于该组件的工作界面的使用是满足该需求的。而且我们知道,Component在完全初始化之前是不会被使用的,在它被销毁之后也不会被使用。为了完成设计,我们需要实现一些隐含的接口。我们希望解决方案足够安全,让我们可能显式地得知组件是否已经完全初始化。为了达到这个目标,我们将实现Initializable和Disposable接口。由于关于环境方面的信息可能发生改变,或者可能需要能定制,我们需要让DocumentRepository实现Configurable接口,Avalon提供的取得所需组件的实例的方法是利用一个ComponentManager。我们需要实现Composable 接口来从ComponentManager取得组件实例。因为DocumentRepository访问数据库中的文档,我们需要做一个决定。我们是要利用Avalon Excalibur DataSourceComponent呢,还是希望自己实现数据库连接管理的代码。在本文中,我们将利用DataSourceComponent。此时,我们的类骨架看起来是这样的:public class DatabaseDocumentRepositoryextends AbstractLogEnabledimplements DocumentRepository , Configurable, Composable, Initializable, Disposable, Component, ThreadSafe{ private boolean initialized = false; private boolean disposed = false; private ComponentManager manager = null; private String dbResource = null; /** * Constructor. All Components need a public no argument constructor * to be a legal Component. */ public DatabaseDocumentRepository() {} /** * Configuration. Notice that I check to see if the Component has * already been configured? This is done to enforce the policy of * only calling Configure once. */ public final void configure(Configuration conf) throws ConfigurationException { if (initialized || disposed) { throw new IllegalStateException ("Illegal call"); } if (null == this.dbResource) { this.dbResource = conf.getChild("dbpool").getValue(); getLogger().debug("Using database pool: " + this.dbResource); // Notice the getLogger()? This is from AbstractLogEnabled // which I extend for just about all my components. } } /** * Composition. Notice that I check to see if the Component has * already been initialized or disposed? This is done to enforce * the policy of proper lifecycle management. */ public final void compose(ComponentManager cmanager) throws ComponentException { if (initialized || disposed) { throw new IllegalStateException ("Illegal call"); } if (null == this.manager) { this.manager = cmanager; } } public final void initialize() throws Exception { if (null == this.manager) { throw new IllegalStateException("Not Composed"); } if (null == this.dbResource) { throw new IllegalStateException("Not Configured"); } if (disposed) { throw new IllegalStateException("Already disposed"); } this.initialized = true; } public final void dispose() { this.disposed = true; this.manager = null; this.dbResource = null; } public final Document getDocument(Principal requestor, int refId) { if (!initialized || disposed) { throw new IllegalStateException("Illegal call"); } // TODO: FILL IN LOGIC }}
您在以上代码中可以发现一些结构模式。当您在设计时考虑到安全性时,您应该在组件中显式地强制实现每个契约。安全强度总是取决于最弱的那一环。只有当您肯定一个组件已经完全初始化以后才能使用它,在它被销毁后,就再也不要用它了。我在这里放上这些逻辑是因为您在编写自己的类时也会采用相同的方式。
组件实例化和管理组件
为了让您能理解容器/组件的关系是如何工作的,我们将先讨论管理组件的手工方式。接下来我们将讨论Avalon‘s Excalibur组件体系结构是如何为您隐藏复杂性的。您仍会发现有些时候宁愿自己管理组件。但在大多数时候,Excalibur的强大能力和灵活性就能满足您的需要。The Manual Method
所有Avalon的组件都是在某处创建的。创建该组件的代码就是该组件的容器。容器负责管理组件从构造到析构的生命周期。容器可以有一个静态的"main"方法,让它能从命令行调用,或者它也可以是另一个容器。在您设计容器时,请记得反向控制的模式。信息和方法调用将只从容器流向组件。颠覆控制(Subversion of Control)
颠覆控制是反向控制的反模式。当您把容器的引用传递给组件时,就实现了颠覆控制。当您让一个组件管理它自己的生命周期时,也属于这种情况。以这种方式操作的代码应该被视为是有缺陷的。当您将容器/组件关系混在一起时,它们的交互将使系统难于调试,并难以审计安全性。
为了管理子组件,您需要在它们整个生命同期都保存对它们的引用。在容器和其它组件可以使用该子组件之前,它必须完成初始化。对我们的DocumentRepository来说,代码看起来可能象下面的样子:class ContainerComponent implements Component, Initializable, Disposable{ DocumentRepository docs = new DatabaseDocumentRepository(); GuardianComponent guard = new DocumentGuardianComponent(); DefaultComponentManager manager = new DefaultComponentManager(); public void initialize() throws Exception { Logger docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); this.docs.enableLogging( docLogger.childLogger( "repository" ) ); this.guard.enableLogging( docLogger.childLogger( "security" ) ); DefaultConfiguration pool = new DefaultConfiguration("dbpool"); pool.setValue("main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this.manager.addComponent( DocumentRepository.ROLE, this.docs ); this.manager.addComponent( GuardianComponent.ROLE, this.guard ); this.docs.compose( this.manager ); this.guard.compose( this.manager ); this.docs.configure(conf); this.guard.initialize(); this.docs.initialize(); } public void dispose() { this.docs.dispose(); this.guard.dispose(); }}
为了简洁,我把显式地检查从以上代码中移去了。您可以看到手工地创建和管理组件是很细节化的工作。如果您忘记做了组件生命周期中的某一步,您就会发现bug。这也需要对您正在实例化的组件有一些深入的了解。另一种做法是给上面的ContainerComponent增加一些方法,来动态地处理组件的初始化。
Automated Autonomy
开发者的本性是懒惰的,所以他们会花时间写一个特别的ComponentManager 作为系统中所有组件的容器。通过这种方式,他们就不必深入地了解系统中所有组件的接口。这可能是个令人沮丧的任务。Avalon的开发者已经创建了这样一个怪兽。Avalon Excalibur的组件体系结构中包括了一个ComponentManager,通过XML的配置文件来控制。当您把管理组件的责任交给Excalibur的ComponentManager时,存在一个折衷。您放弃了对CompomentManager中包含哪些组件的精细控制。但是,如果您的系统相当大,您会发现手工控制是一项令人沮丧的工作。在这种情况下,出于对系统稳定性的考虑,最好由一个地方集中式地管理系统中所有的组件。既然可以与Excalibur的组件体系结构有不同中层次的集成程度,我们将从最低层次开始。Excalibur有一组ComponentHandler对象,它们作为每类组件独立的容器。它们管理您的组件的整个生命周期。让我们引入生存方式(lifestyle)接口的概念。一个生存方式接口描述了系统是怎样对待一个组件的。既然组件的生存方式对系统运行会产生影响,我们就需要讨论当前的一些生存方式所暗含的意义:·  org.apache.avalon.framework.thread.SingleThreadedo  不是线程安全的或可重用的。o  如果没有指定其它生存方式方式接口,系统就认为是这个。o  在每次请求组件时,都会创建一个全新的实例。o  实例的创建和初始化被推迟到请求组件时进行。·  org.apache.avalon.framework.thread.Threadsafeo  组件是完全可重入的,并符合所有的线程安全的原则。 o  系统创建一个实例,所有Composable组件对它的访问是共享的。o  实例的创建和初始化是在ComponentHandler创建时完成的。·  org.apache.avalon.excalibur.pool.Poolableo  不是线程安全的,但是是完全可重用的。o  创建一组实例放在池中,当Composable组件请求时,系统提供一个可用的。o  实例的创建和初始化是在ComponentHandler创建时完成的。ComponentHandler接口处理起来是很简单的。你通过Java类、Configuration对象、ComponentManager对象、Context对象和RoleManager对象来初始化构造方法。 如果您知道您的组件将不需要上述的某一项,您可以在它的位置上传一个null。 在这之后,当您需要对该组件的引用时,您就调用"get"方法。当您用完之后,您调用"put"方法将组件归还给ComponentHandler。 以下的代码便于我们理解这一点。class ContainerComponent implements Component, Initializable, Disposable{ ComponentHandler docs = null; ComponentHandler guard = null; DefaultComponentManager manager = new DefaultComponentManager(); public void initialize() throws Exception { DefaultConfiguration pool = new DefaultConfiguration("dbpool"); pool.setValue("main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this.docs.configure(conf); this.docs = ComponentHandler.getComponentHandler( DatabaseDocumentRepository.class, conf, this.manager, null, null); this.guard = ComponentHandler.getComponentHandler( DocumentGuardianComponent.class, null, this.manager, null, null); Logger docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); this.docs.enableLogging( docLogger.childLogger( "repository" ) ); this.guard.enableLogging( docLogger.childLogger( "security" ) ); this.manager.addComponent(DocumentRepository.ROLE, this.docs); this.manager.addComponent(GuardianComponent.ROLE, this.guard); this.guard.initialize(); this.docs.initialize(); } public void dispose() { this.docs.dispose(); this.guard.dispose(); }}
这里,我们只少写了几行代码。我们还是手工地创建了Configuration对象,还是设置了Logger,还是不得不对ComponentHandler对象进行初始化和销毁。 这里我们所做的只是防止受到接口变化的影响。您会发现用这种方式对代码有好处。Excalibur所做的更进了一步。大多数复杂的系统都有一些配置文件。它们允许管理员调整关键的配置信息。 Excalibur可以用以下的格式读取配置文件,并从中创建系统的组件。<my-system> <component role="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector" class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"> <component-instance name="documents" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"> <pool-controller min="5" max="10"/> <auto-commit>false</auto-commit> <driver>org.gjt.mm.mysql.Driver</driver> <dburl>jdbc:mysql:localhost/mydb</dburl> <user>test</user> <password>test</password> </component-instance> <component-instance name="security" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"> <pool-controller min="5" max="10"/> <auto-commit>false</auto-commit> <driver>org.gjt.mm.mysql.Driver</driver> <dburl>jdbc:mysql:localhost/myotherdb</dburl> <user>test</user> <password>test</password> </component-instance> </component> <component role="org.apache.bizserver.docs.DocumentRepository" class="org.apache.bizserver.docs.DatabaseDocumentRepository"> <dbpool>documents</dbpool> </component> <component role="org.apache.bizserver.docs.GuardianComponent" class="org.apache.bizserver.docs.DocumentGuardianComponent"> <dbpool>security</dbpool> <policy file="/home/system/document.policy"/> </component></my-system>
根元素可以由您任意指定。 您会注意到我们已经定义了一些组件。 我们有了熟悉的DocumentRepository类和GuardianComponent类,以及一些Excalibur DataSourceComponent类。 而且,现在我们对Guardian组件有了一些特定的配置信息。 为了把这些系统读入您的系统,Avalon框架为您提供了一些方便:DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();Configuration systemConf = builder.buildFromFile("/path/to/file.xconf");
这确实对我们前面手工构建配置元素的代码起到了简化作用,而且它限制了我们在编程时需要明确了解的信息。 让我们再看一眼Container类,看看是否真的省了一些事。记住我们指定了5个组件( ComponentSelector算作是一个组件), 以及每个组件的配置信息。class ContainerComponent implements Component, Initializable, Disposable { ExcaliburComponentManager manager = new ExcaliburComponentManager(); public void initialize() throws Exception { DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); Configuration sysConfig = builder.buildFromFile("./conf/system.xconf"); this.manager.setLogger( Hierarchy.getDefaultHierarchy() .getLoggerFor("document") ); this.manager.contextualize( new DefaultContext() ); this.manager.configure( sysConfig ); this.manager.initialize(); } public void dispose() { this.manager.dispose(); }}
难道不令人惊奇?我们对数量超过两倍的组件进行了初始化,而代码量减少了一倍多(6行代码,而不是13行)。 这个配置文件有一个缺点,看起来有点疯狂,但它将需要写的代码数量降到了最低。在ExcaliburComponentManager的背后发生了很多的活动。 对配置文件中的每个"component"元素,Excalibur为每个类的条目(entry)创建了一个ComponentHandler,并建立起与角色(role)的对应关系。 "component"元素和它的所有子元素是对组件的配置。当组件是一个ExcaliburComponentSelector时, Excalibur会读入每个"component-instance"元素并执行和前面同类型的操作,这次是与hint entry建立对应关系。让配置文件好看一些
我们可以使用别名来改变配置文件的外观。Excalibur使用一个RoleManager为配置系统提供别名。RoleManager可以是您专门创建的一个类,也可以使用DefaultRoleManager并传入一个Configuration对象。 如果我使用DefaultRoleManager,我将把角色配置文件和系统的其它部分藏在jar文件中。这是因为角色配置文件将只由开发者改动。 下面是RoleManager接口:interface RoleManager{ String getRoleForName( String shorthandName ); String getDefaultClassNameForRole( String role ); String getDefaultClassNameForHint( String hint, String shorthand );}
让我们来看一下Excalibur是如何使用我们的框架中的RoleManager的。首先,Excalibur循环读入根元素的所有子元素。 这包括了所有的"component"元素,但这次Excalibur并不识别元素的名称,它询问RoleManager 对这个组件我们将使用什么角色。如果RoleManager返回null, 那么该元素和它所有的子元素都被忽略。接下来, Excalibur 从角色名称中导出类名。最后的方法是动态地将类名与ComponentSelector的子类型对应起来。Excalibur提供了一个RoleManager的缺省实现,它使用一个XML配置文件。标记相当简单,它把所有您不希望管理员看到的附加信息都隐藏起来的。<role-list> <role name="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector" shorthand="datasources" default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"> <hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSourceComponent"/> <hint shorthand="j2ee" class="org.apache.avalon.excalibur.datasource.J2eeDataSourceComponent"/> </role> <role name="org.apache.bizserver.docs.DocumentRepository" shorthand="repository" default-class="org.apache.bizserver.docs.DatabaseDocumentRepository"/> <role name="org.apache.bizserver.docs.GuardianComponent" shorthand="guardian" default-class="org.apache.bizserver.docs.DocumentGuardianComponent"/></role-list>
为了使用RoleManager,您需要改变容器类中的"初始化"方法。您将使用配置构造器(configuration builder)通过这个文件来构造一个Configuration树。 请记住,如果您打算使用一个RoleManager,您必须在调用"configure"方法之前调用"setRoleManager"方法。 为了展示您如何从类装入器中取得这个XML文件,我将在下面展示该技巧:DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();Configuration sysConfig = builder.buildFromFile("./conf/system.xconf");Configuration roleConfig = builder.build( this.getClass().getClassLoader() .getResourceAsStream("/org/apache/bizserver/docs/document.roles"));DefaultRoleManager roles = new DefaultRoleManager();roles.enableLogging(Hierarchy.getDefaultHierarchy().getLoggerFor("document.roles"));roles.configure(roleConfig);this.manager.setLogger( Hierarchy.getDefaultHierarchy() .getLoggerFor("document") );this.manager.contextualize( new DefaultContext() );this.manager.setRoleManager( roles );this.manager.configure( sysConfig );this.manager.initialize();
既然我们增加了6行代码,就要看一下它带来了什么好处。 我们最终的配置文件可以这样写:<my-system> <datasources> <jdbc name="documents"> <pool-controller min="5" max="10"/> <auto-commit>false</auto-commit> <driver>org.gjt.mm.mysql.Driver</driver> <dburl>jdbc:mysql:localhost/mydb</dburl> <user>test</user> <password>test</password> </jdbc> <jdbc name="security"> <pool-controller min="5" max="10"/> <auto-commit>false</auto-commit> <driver>org.gjt.mm.mysql.Driver</driver> <dburl>jdbc:mysql:localhost/myotherdb</dburl> <user>test</user> <password>test</password> </jdbc> </datasources> <repository> <dbpool>documents</dbpool> </repository> <guardian> <dbpool>security</dbpool> <policy file="/home/system/document.policy"/> </guardian></my-system>
正如您所看到的那样,与前面的文件相比,这个文件的可读性要强很多。 现在我们可以为系统增加任意数目的组件,而不需要写更多的代码来支持它们。

使用该组件
现在我们已经创建了我们的组件,我们将使用它们。不管组件是如何被初始化和管理的,您访问组件的方法都是一样的。 您必须实现Composable接口,这样才能从ComponentManager得到一个引用。 ComponentManager保存着所有您需要的组件的引用

Avalon Framework概念