Jakarta main

Avalon main

About


章节


原文链接


Printer Friendly


中文译者


实现梦想

我们将向你展示怎样使用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 DatabaseDocumentRepository
extends AbstractLogEnabled
implements 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.SingleThreaded

    • 不是线程安全的或可重用的。

    • 如果没有指定其它生存方式方式接口,系统就认为是这个。

    • 在每次请求组件时,都会创建一个全新的实例。

    • 实例的创建和初始化被推迟到请求组件时进行。

  • org.apache.avalon.framework.thread.Threadsafe

    • 组件是完全可重入的,并符合所有的线程安全的原则。

    • 系统创建一个实例,所有Composable组件对它的访问是共享的。

    • 实例的创建和初始化是在ComponentHandler创建时完成的。

  • org.apache.avalon.excalibur.pool.Poolable

    • 不是线程安全的,但是是完全可重用的。

    • 创建一组实例放在池中,当Composable组件请求时,系统提供一个可用的。

    • 实例的创建和初始化是在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保存着所有您需要的组件的引用。为了讨论方便起见,我们将假定我们得到的ComponentManager 是按照前一节的最终的配置文件来配置的。 这就是说我们有一个Repository, 一个Guardian, 和两个DataSources。

使用组件管理基础结构的原则

组件管理基础结构要求您释放您得到引用的组件。这个限制的原因是为了能正确地管理组件资源。ComponentManager的设计考虑到您对特定的角色有不同类型的组件。 ComponentSelector的另一个独特的方面是它也被设计为一个组件。这使我们可以从一个ComponentManager取得一个ComponentSelector。

有两种合法的方式来处理对外部组件的引用。您可以在初始化过程中得到引用,在销毁时释放它们。 您也可以把处理组件的代码放在try/catch/finally语句块中。两种方法各有优缺点。

Initialization and Disposal Approach

class MyClass implements Component, Composable, Disposable
{
    ComponentManager manager;
    Guardian myGuard;

    /**
     * Obtain a reference to a guard and keep the reference to
     * the ComponentManager.
     */
    public void compose(ComponentManager manager)
        throws ComponentException
    {
        if (this.manager == null)
        {
            this.manager = manager;
            myGuard = (Guardian) this.manager.lookup(Guardian.ROLE);
        }
    }

    /**
     * This is the method that uses the Guardian.
     */
    public void myMethod()
        throws SecurityException
    {
        this.myGuard.checkPermission(new BasicPermission("test"));
    }

    /**
     * Get rid of our references
     */
    public void dispose()
    {
        this.manager.release(this.myGuard);
        this.myGuard = null;
        this.manager = null;
    }
}

        

从示例代码中您可以看到,照这样做很容易。当该对象第一次接收到ComponentManager时,它取得了一个Guardian组件的引用。 如果您可以保证Guardian组件是线程安全的(实现了ThreadSafe接口),那么就只需要做这些事。 不幸的是,从长远来看,您不能保证这一点。为了能正确地管理资源,在用完组件之后,我们必须释放对组件的引用。 这就是为什么我们保持一个对ComponentManager的引用的原因。

这种方式的主要不利之处在于处理组件池中的组件时。对组件的引用维系着该组件的生命。 如果该对象的生存期很短,这可能不是个问题;但是如果该对象是一个由Excalibur组件管理体系结构所管理的组件,只要有对它的引用,它的生存期就会继续。这意味着我们实际上把组件池变成了一个组件工厂。

这种方式的主要好处是,得到和释放组件的代码很清楚。 您不必去理解异常处理的代码。

另一个细微差别是,您把Guardian的存在与初始化这个对象的能力捆绑在了一起。 一旦在一个对象的初始化阶段抛出一个异常, 你就只好认为该对象不是一个有效的对象。有时您希望当要求的组件不存在时就让程序失败掉,那么这就不是问题。 在设计组件时,您确实需要注意到这一层隐含的意思。


Exception Handling Approach

class MyClass implements Composable, Disposable
{
    ComponentManager manager;

    /**
     * Obtain a reference to a guard and keep the reference to
     * the ComponentManager.
     */
    public void compose(ComponentManager manager)
        throws ComponentException
    {
        if (this.manager == null)
        {
            this.manager = manager;
        }
    }

    /**
     * This is the method that gets the Guardian.
     */
    public void myMethod()
        throws SecurityException
    {
        Guardian myGuard = null;
        try
        {
            myGuard = (Guardian) this.manager.lookup(Guardian.ROLE);
            this.criticalSection(myGuard);
        }
        catch (ComponentException ce)
        {
            throw new SecurityException(ce.getMessage());
        }
        catch (SecurityException se)
        {
            throw se;
        }
        finally
        {
            if (myGuard != null)
            {
                this.manager.release(myGuard);
            }
        }
    }

    /**
     * Perform critical part of code.
     */
    public void criticalSection(Guardian myGuard)
        throws SecurityException
    {
        myGuard.checkPermission(new BasicPermission("test"));
    }
}

        

如您所见,这段代码有些复杂。为了能理解它,您需要理解异常处理。 这可能不是问题,因为绝大多数Java开发者都知道如何处理异常。 用这种方式,您不需要太担心组件的生存方式问题, 因为一旦我们不需要它时,就释放了它。

这种方式的主要不利之处是增加了异常处理代码,因为较为复杂。 为了能将复杂度降到最低,让代码更易于维护,我们把工作代码提取出来,放在另一个方法中。 请记住在try语句块中,我们想得到多少组件的引用,就可以得到多少。

这种方式的主要好处是您可以更有效率地管理组件引用。 同样,如果您使用的是ThreadSafe组件,就没有实质差别,但如果您使用的是组件池里的组件时,就有差别了。 在每次您使用一个组件,取得一个新的引用时,有一点轻微的开销,但是被迫创建一个新的组件实例的可能性大大降低。

像初始化和销毁的方式一样,您也必须了解一个微妙的差别。 如果管理器找不到组件,异常处理的方式不会让程序在初始化时失败掉。 如前所述,这并不是完全没好处的。 很多时候,您希望某个组件存在,但是如果期望的组件不存在,程序也不需要失败掉。



从ComponentSelector取得一个组件

对于大多数操作,您只需要使用ComponentManager。 既然我们决定我们需要DataSourceComponent的多个实例, 我们就需要知道如何得到我们想要的那个实例。 ComponentSelector比ComponentManagers要稍复杂一些, 因为处理时有暗示要取得想要的引用。 一个组件属于一个特定的角色,这我们已经说得很清楚了。 但是,有时候我们需要从一个角色的多个组件中选择一个。 ComponentSelector使用一个任意的对象来作为暗示。 大多数时候,这个对象是一个String,尽管您可能会希望使用一个Locale对象来取得一个正确国际化的组件。

在我们已建立起来的系统中, 我们选择使用字符串来选择DataSourceComponent的正确实例。 我们甚至给了自己一个Configuration元素,来指明为了得到正确的组件,需要的字符串是什么。 这是一个好的实践,可以照着做,因为它使系统管理更容易。 比起要系统管理员记住这些神奇的配置值来,这便他们更容易看到对其它组件的引用。

从概念上来看,从ComponentSelector取得一个组件与从ComponentManager取得组件并无差别。 你只多了一个步骤。 请记住ComponentSelector也是一个组件。 当您查找ComponentSelect的角色时,ComponentManager 将准备好ComponentSelector组件并返回给您。 然后您需要通过它来选择组件。 为了说明这一点,我将扩展前面讨论的异常处理方式的代码。

public void myMethod()
    throws Exception
{
    ComponentSelector dbSelector = null;
    DataSourceComponent datasource = null;
    try
    {
        dbSelector = (ComponentSelector)
                this.manager.lookup(DataSourceComponent.ROLE + "Selector");
        datasource = (DataSourceComponent)
                dbSelector.select(this.useDb);

        this.process(datasource.getConnection());
    }
    catch (Exception e)
    {
        throw e;
    }
    finally
    {
        if (datasource != null)
        {
            dbSelector.release(datasource);
        }

        if (dbSelector != null)
        {
            this.manager.release(dbSelector);
        }
    }
}

      

您可以看到,我们通过使用指定组件的角色得到了ComponentSelector的引用。 我们遵守了前面提到的角色命名规范,在角色名的后名加上"Selector"作为后缀。 您可以使用一个静态的接口来处理系统小所有的角色名,以减少代码中字符串连接的次数。这样做也是完全可以接受的。

接下来,我们得从ComponentSelector到了DataSource组件的引用。 我们的示例代码假定我们已经从Configuration对象取得了所需的信息并把它放在一个名为"useDb"的类变量中。



Excalibur的工具类

最后这一节是向您介绍Apache Avalon Excalibur带的几类组件和工具类。 这些工具类是健壮的, 完全可以在实际生产系统中使用。 我们有一个非正式的分级式项目称为"Scratchpad",在这个项目中,我们解决潜在新工具类的实现细节问题。 Scratchpad中的工具类的品质各有不同,它们的使用方式也不能保证不变,尽管您可能觉得用起来不错。

命令行接口(Command Line Interface,CLI)

CLI工具类在一些项目中使用,包括Avalon Phoenix和Apache Cocoon,用于处理命令行参数。它提供了打印帮助信息的机制,并能够以短名字或长名字的方式来处理参数选项。


集合工具类

集合工具类对JavaTM Collections API进行了一些增强。 这些增强包括在两个list中找出交叉处的功能和一个PriorityQueue ,它是对Stack 的增强,允许对象的优先级改变简单的先进后出的Stack实现。


组件管理

我们已经在前面讨论了这方面的用法。这是Excalibur中最复杂的怪兽,但是它在很少的几个类中提供了很多的功能。在简单的SingleThreadedThreadSafe两种管理类型之外,还有一种Poolable类型。如果一个组件实现了Excalibur的Poolable接口,而不是SingleThreaded接口, 那么它将维护一个组件池并重用实例。大多数情况下这工作得很好。 在少数组件不能重用的情况下,就使用SingleThreaded接口。


LogKit管理

Avalon开发团队意识到许多人需要一种简单的机制来创建复杂的日志目标层次结构。 出于RoleManager类似的想法,团队开发了LogKitManager,可以被前面提到的Excalibur Component Management系统使用。基于"logger"属性,它将为不同的组件给出相应的 Logger 对象。


线程工具类

concurrent包提供了一些辅助多线程编程的类:Lock (mutex的实现), DjikstraSemaphore, ConditionalEvent, 和 ThreadBarrier.


Datasources

这是根据javax.sql.DataSource类设计的,但是作了简化。 DataSourceComponent有两个实现:一个显式地使用JDBC连接池,另一个使用J2EE 应用服务器的javax.sql.DataSource 类。


输入/输出 (IO) 工具类

IO工具类提供了一些FileFilter类以及File和IO相关的工具类。


池实现

Pool实现在各种情况下都能使用的池。其中有一个实现非常快, 但只能在一个线程中使用,用来实现FlyWeight模式是不错的。还有一个DefaultPool,它不管理池中对象的数目。SoftResourceManagingPool在对象被归还时判断是否超过一个阈值,如果超过,就让对象“退役”。最后,HardResourceManagingPool在您达到对象的最大数目时会抛出一个异常。后面的三个池都是ThreadSafe的。


Property工具类

Property工具类与Context对象一起使用。它们使您可以扩展Resolvable对象中的"variables"。它是这样工作的:"${resource}" 将去寻找一个名为"resource"的Context的值,并用这个值来代替这个符号。





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