posts - 40,  comments - 7,  trackbacks - 0
          Avalon的簡要歷史以及創建它所有的設計原則概述

          事情是從Apache JServ項目開始的。Stefano Mazzocchi和其它協助開發Apache JServ的人員認識到項目中所用到的一些模式很通用,足以用于創建一個服務器框架。 在1999年1月27日,星期三(在JServ 1.0b發布大約一個月后),Stefano拿出一份建議書,建議啟動一個名為Java Apache Server Framework的項目。它的目標是成為Apache所有Java服務器代碼的基礎。想法是通過提供一個框架,將跨項目的一些組件和重用代碼集中在一起。

          Stefano Mazzocchi,Federico Barbieri和Pierpaolo Fumagalli創建了最初的版本。在2000年末,Berin Loritsch和Peter Donald參加到項目中來。那時,Pierpaolo和Stefano已轉向其它項目的開發,Java Apache Server Framework開始被稱為Avalon。這五個開發者是框架目前版本所使用的設計和概念的主要負責人。當前版本與2000年6月發行的版本非常相似。實際上,主要的區別是對包重新組織,以及將項目劃分為子項目。同樣的設計模式和接口今天依然存在。
          分解一個系統

          您是如何決定由哪些東西組成一個組件的? 關鍵是定義您的解決方案所需的設施,以便能有效率地進行操作。

          我們將使用一個假想的業務服務器來展示如何識別和確定服務與組件。在我們定義了一些系統用到的服務之后,我們將以這些服務中的一個為例,定義該服務所需的不同組件。我的目標是傳遞給您一些概念,這些概念有助于您把您的系統定義成可管理的部分。


          系統分析——識別組件

          盡管提供一個完整全面的方法學不是本篇的范圍,我還是愿意討論幾點問題。我們將從組件和服務的面向實現的定義開始,然后提供一個實踐的定義。

          組件

          一個組件是一個工作接口和該工作接口的實現的組合。使用組件提供了對象間的松耦合,允許改變實現而不影響使用它的代碼。

          服務

          一個服務由一個或更多的組件組成,提供了一種完整的解決方案。服務的例子包括協議處理器、任務調度器、認證和授權服務等等。

          盡管這些定義提供了一個起點,但它并沒有提供一個完整圖景。為了把一個系統(定義為一組設施,組成一個項目)分解為必要的組成部分,我建使用自頂向下的方式。采用這種方式可以在您了解到底有哪些設施之前,避免陷入到細節的泥潭中。

          確定您的項目范圍

          您的項目預期完成什么功能,一開始您總要有個大致的想法。在商業世界里,初始工作說明(initial statement of work )就是完成這項工作的。在開放源代碼的世界里,這通常是由一個想法或一個腦力激蕩的過程完成的。我想不論如何強調具備一個項目的高層視圖的重要性都是不過份的。

          很明顯,一個大項目將由許多不同的服務組成,而小項目只有一兩個服務。如果您開始感到有點不知所措,只需提醒自己大項目實際上是一把大傘下的許多小項目。最終,您將能理解整個系統大的圖景。



          工作說明:業務服務器

          業務服務器(Business Server)是一個假想的項目。出于我們討論問題的目的,它的功能是處理銷售訂單,自動向客戶發出賬單,并管理存貨控制。銷售訂單到達時必須得到處理,通過某種類型的事務系統。在銷售訂單填寫30天后,服務器自動向客戶發出賬單。庫存由服務器和工廠或倉庫的當前庫存量同時來管理。該業務服務器將是一個分布式系統,每個服務器將通個一個消息服務來與其它服務器通信。


          發現服務

          我們將使用這個Business Server項目來發現服務。考慮上面的過于概括性的工作說明,我們立即可以看到項目描述中定義的一些服務。服務的清單可被分成兩大類:顯式的服務(可以從工作說明中直接導出的服務)和隱式的服務(根據相似的工作發現的服務,或對顯示服務起支持作用的服務)。請注意,實現系統的公司不必親自開發所有的服務——有一些可作為商業解決方案購買。在那些情況下,我們可能會開發一個封裝層(wrapper),以便我們能夠以確定的方式與商業產品實現互操作。實現系統的公司將構建大部分的服務。

          顯式的服務

          從工作說明中,我們可以快速導出一些服務。但這種初始分析并不意味我們的工作已完成,因為某些服務的定義需要其它服務的存在才能保證。

          事務處理服務

          工作說明明確指出“銷售訂單到達時必須得到處理 ”。這表明我們需要有一種機制能夠接受銷售請求并自動地處理它們。這與web服務器工作的方式相似。它們接收到對資源的請求,進行處理并返回一個結果(如HTML頁面)。這被稱作是事務處理。

          完整地說起來,存在不同類型的事務處理。這種一般性的事務處理服務極有可能必須分解為一些更特殊的東西,如“銷售訂單處理器“。具體方法取決于您的服務的通用性。在可用性和可重用性之間存在一種平衡。服務越是通用,它就越可重用。通常它也就越難于理解。


          調度服務

          在某些情況下,當事務完成,過了一段特定的時間之后,必須調度某個事件。而且,庫存控制過程必需能周期性地開出采購訂單。因為工作說明中指出“ 在銷售訂單填寫30天后,服務器自動向客戶發出賬單” ,所以我們需要一個調度服務。所幸的是Avalon Cornerstone為我們提供了一個,這樣我們就不必再自己寫一個了。


          消息服務

          工作說明中指出,在我們的分布式系統中“每個服務器將通個一個消息服務來與其它服務器通信“。讓我們來考慮這個問題,有時用戶需要一個特定的產品或一種他們想用的方法。消息服務是利用其它公司產品的一個首選例子。極有可能,我們將采用Java Messaging Service (JMS) 來作為Messaging Service的接口。因為JMS是一個標準,它的接口不大可能短期內發生變化。

          從實踐經驗上來說,一個定義良好的面向消息的系統在可擴展性方面要強于面向對象的系統(如EJB)。可擴展性更好的一個原因是消息通常并發內存開銷較小。另一個原因是它更容易把消息處理的負載分散到所有服務器上去,而不是把所有的處理集中在少量的服務器集群上(甚至是在一臺服務器上)。


          庫存控制服務

          盡管這不是一個教科書上的經典服務,但它是這個系統的一項需求。庫存控制服務固定地監視工廠或倉庫存貨的記錄,當存貨開始不足時觸發一些事件。



          隱式的服務

          運用在過去系統中獲得的經驗,將系統更進一步分解出其它服務,將得到沒有明確指出但又是系統需要的一些服務。因為篇幅關系,我們將不做全面地分解。

          認證和授權服務

          認證和授權服務沒有在工作說明中明確地提到,但是所有的業務系統必須認真考慮安全性。這意味著系統所有的客戶端都必須經過認證,用戶的所有行為都必須經過授權。


          工作流自動化服務

          工作流自動化是企業系統中的一個熱門開發領域。如果您不使用第三方的工作流管理服務器,您就需要自己寫一個。通常工作流自動化所做的是使用軟件系統來安排貫穿公司業務過程的任務。更多的信息請參考Workflow Management Council的網站http://www.wfmc.org/


          文檔中心服務

          作為一個任務的當前狀態信息,"文檔中心"這個詞的定義很不精確。換言之,當公司接到一份購買訂單時,我們的系統需要能夠存儲并重新調出購買訂單信息。出賬單和系統中其它任何過程,從庫存到新的用戶請求都有同樣的需求。



          小結

          我希望Business Server項目的服務的例子可以幫助您發現更多。您會發現,當您從較高的抽象層逐漸轉向較低的抽象層時,會發現需要更多類型的服務,如用于在一個打開端口上處理請求的連接服務。我們定義的某些服務將通過第三方的系統來實現,如消息服務和工作流管理服務。對這些服務來說,使用一個標準接口是最符合您的利益的,這樣您可以在以后更換供應商。有些服務實際上是由多個服務組成的大服務。有些服務Avalon Excalibur或Avalon Cornerstone中已經提供了。

          在發現一個系統中的服務時,應該牢記的一件事是:一個服務應該是一個高層子系統。這將有助于您通過分析師團隊來定義組件。因為我們已識別出了主要的服務,您可以讓多個個人(或團隊)并行地分解每個服務。子系統邊界也定義良好,發生重疊的可能性很小。如果您決定進行并行分析,您應該回過頭來識別通用的組件,以便能夠盡可能地重用。

          UML Diagram for the Business Server
          Berin Loritsch, 2001
          • Berin Loritsch, 2001


          發現組件

          我們將以前面提到的文檔中心服務為例來說明識別合適的組件的過程。為討論方便起見,我們現在來列出文檔中心服務的需求。文檔中心將采用一個數據庫來作為持久存儲,對客戶端授權,在內存中緩存文檔。

          組件的實踐性定義

          當我們談論組件時,您考慮問題的角度應該是:我的服務需要操作哪些設施?Avalon相信將系統投影(cast)的概念。系統的開發者會面對一個組件的職責列表,組件則被稱為它的角色(role)。

          什么是角色?

          角色的概念來自劇院。一部戲劇、音樂劇或電影片都會有一定數量的角色,由演員來扮演。盡管演員似乎從不短缺,角色的數量卻是有限的。演出的腳本 定義了角色的功能或行為。如同劇院里發生的一樣,腳本決定了您如何與組件交互。考慮系統中的不同角色,您會將組件的投影為角色,并與之對話。

          一個角色是一類組件的契約。例如,我們的文檔中心服務需要操作數據庫。Avalon Excalibur定義了一個組件,符合"Data Source"腳色的需要。在Excalibur中有兩個不同的組件,都能滿足該角色的需要。 具體使用哪一個取決于服務所處的環境,但是它們都滿足相同的契約。大量基于Avalon的系統對每個角色將只用到一個活動的組件。腳本就是工作接口:其它組件與之交互的接口。

          在確定組件的接口時,必須有確定的契約并牢記在心。契約規定了組件的使用者必須提供什么,以及組件生產出什么。有時在契約中必須包括使用語義。一個例子是臨時存儲組件和持久存儲組件之間的區別。當接口和協議定義好之后,您就可以致力于實現。



          怎樣算是一個好的候選組件?

          在我們的文檔中心服務中,我們已識別了四個可能的組件:DataSourceComponent (來自Excalibur)、 Cache、Repository、Guardian。您應該尋求那些很可能有多種實現的角色,與這些實現的交互可以無縫地進行。

          通個這個例子,您會發現一些情況下您需要使用可替換的設施。大多數情況下,您只會用到該設施的一種實現,但您需要能獨立地升級它而不影響到系統的其它部分。其它情況下,您需要根據環境的不同使用不同的實現。例如,Excaliber定義的"Data Source"通常會自己處理所有的JDBC連接池,但有時你可能希望利用Java 2 Enterprise Edition(J2EE)中提供的設施。Excalibur解決這個問題的辦法是,一個"Data Source"組件直接管理JDBC連接和池,另一個組件使用Java's Naming and Directory Interface (JNDI) 來得到特定的連接。


          怎樣不算是一個好的組件?

          習慣于使用JavaBeans的人喜歡把所有的東西都實現為一個JavaBean。這意味著從數據模型到事務處理的一切東西。如果您用這種方式來處理組件,您可能會得到一個過于復雜的系統。把組件視為一個服務或設施的模型,而不是數據的模型。您可以有從其它資源拉數據的組件,但數據還是應該保持為數據。在Avalon Excalibur中這種哲學的一個例子是連接(Connection)不是一個組件。

          另一個例子是我們前面提到的Guardian組件。可能存在的爭議是,Guardian所包含的邏輯與文檔中心服務太相關,不能做為一個組件用在完全不同的服務中。盡管管理復雜性有多種方式,也有多種方式讓它變得靈活,但有時為它付出額外的工作并不值得。在這種情況下,您必仔細權衡您的決定。如果一個潛在組件的邏輯將被一致地應用,那么將它作為一個組件可能是有意義的。在一個系統中可以有一個組件的多個實例,它們可以在運行時進行選擇。如果潛在組件的邏輯只是根據另外一個組件來確定的,也許可以把這些邏輯放到另外的那個組件中去。通過Guardian組件和Repository組件的例子,我們可以辯稱Guardian太專注于Repository,不是作為一個組件來實現的。


          分解文檔中心服務

          我們將列出將要實現的組件,以及它們的角色、根本原因和來源(如果組件已經存在的話)。

          DocumentRepository

          DocumentRepository是整個服務的父組件。在Avalon中,服務實現為Block,Block是一個特定類型的組件。Block必須有一個工作接口,擴展了Service marker接口。Block接口也擴展了Avalon的Component接口。請注意,Block和Service是包含在Avalon Phoenix中的接口。最后,Service從技術上說仍是一種特定類型的Component。

          DocumentRepository是我們從持久存儲中取得Document對象的方法。它與服務中的其它組件交互,以提供安全性、功能性和速度。這個特定的DocumentRepository會與數據庫連接,在內部使用數據庫的邏輯來建造Document對象。


          DataSourceComponent

          DataSourceComponent由Avalon Excalibur提供。它是我們獲得有效的JDBC連接對象的方式。


          Cache

          Cache是一個短期內存中的存儲設施。DocumentRepository將用它來保存Document對象,并通過一個散列算法來引用。為了提高Cache組件的可重用性,存儲的對象必須實現一個Cacheable接口。


          Guardian

          Guardian組件的作用是基于參與者管理許可。Guardian將從數據庫中裝入許可規則集。Guardian將使用標準Java安全模型,以保證對特定Document的訪問。



          小結

          到目前為止,您應該對怎樣才算是一個好組件有一些認識了。例子描述了在文檔中心服務中的所有組件,簡要介紹了它們將完成的工作。快速瀏覽這個列表,它體現了將設施實現為組件而不是數據的方法。到目前為止,你應該能夠確定您的服務需要操作什么組件。

          框架和基礎

          我們將描述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] orparameterize() [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對象,測試合適的組件或最低版本號也會更容易。

          實現夢想

          我們將向你展示怎樣使用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的值,并用這個值來代替這個符號。


          結論

          Avalon經受了時間的考驗,可以供您使用。本部分提供的證據可以幫助說服您自己和其它人,使用一個成熟的框架比自己創建一個要好。

          您可能已經被說服了,但需要一些幫助來說服您的同事,讓他們相信Avalon是正確的選擇。 也許您也需要說服您自己。 不管怎樣,本章將幫助您整理思路,并提供一些有說服力的證據。 我們需要與對開放源代碼模式的(Fear, Uncertainty, and Doubt ,FUD)作斗爭。 關于開放源代碼的有效性的證據,我推薦您閱讀Eric S. Raymond對這一主題的優秀論述N400017. 不論您對他所持觀點的看法如何,他寫的文章匯編成的書The Cathedral and the Bazaar 將提供使人從總體上接受開放源代碼的信息。


          Avalon能工作

          我們的底線是Avalon完成了它最初設計時要達到的目標。 Avalon沒有引入新的概念和思想,而是使用了一些經受時間考驗的概念,并將它們規范化。 影響Avalon設計的最新的概念是分離考慮(Separation of Concerns)模式,它大約是在1995提出的。即使在那時候, 分離考慮也是一種系統分析技術的規范化方法。

          Avalon的用戶群數以百計。 一些項目如Apache Cocoon, Apache JAMES, 和Jesktop是建立在Avalon之上的。這些項目的開發者是Avalon Framework的用戶。 因為Avalon有如此眾多的用戶,它得到了很好的測試。

          由最優秀的人設計

          Avalon的作者認識到,我們不是服務器端計算的唯一一群專家。 我們使用了來自其它人的研究的概念和想法。 我們響應來自用戶的反饋。Avalon不僅僅是由前面介紹的5個開發者設計的,他們帶來的是反向控制、分離考慮和面向組件編程的思想,并設計了它。

          開放源代碼的優秀之處在于,得到的結果是最佳的思想和最佳的代碼的融合。 Avalon經過一了些測試想法的階段并拒絕了一些想法,因為還有更好的解決方案。 您可以利用Avalon開發小組所獲得的知識,并在您自己的系統中運用。 您可以在自己的項目中使用Excalibur中預先定義好的組件,它們經過了測試,可以在重負載下運行無誤。


          兼容性的許可證

          Apache Software License (ASL)可以兼容其它任何已知的許可證。 已知的最大例外是GNU Public License (GPL) 和Lesser GNU Public License (LGPL). 重要的是ASL對合作開發相當友好,如果您不想的話,也不會強迫您發布源代碼。 Apache Software Foundation名下的德高望眾的HTTP服務器用的是相同的許可證。


          集群式的研發

          大多數的Avalon用戶以某種方式做出他們的貢獻。這把開發,調試和文檔的工作量分散到了一些用戶身上。 這也表明Avalon的代碼經過了更廣泛的對等復查,這在公司開發中是不可能做到的。 而且,Avalon的用戶支持Avalon。 盡管開放源代碼的項目通常沒有一個幫助平臺或電話支持熱錢,但是我們有一個郵件列表。 您的許多問題可以通過郵件列表得到很快的回答,比一些支持熱線還要快。


          簡化的分析和設計

          基于Avalon開發有助于開發者達到一種精神狀態。 在這種精神狀態下,開發者的工作集中在如何發現組件和服務上。 既然關于組件和服務的生存期的細節問題都已得到分析和設計,開發者只需選擇他們需要的就行了。

          需要重點指出的是,Avalon的開始并不是取代了傳統的面向對象分析和設計,而是對它進行了增強。 您還是使用以前所用的技術,只不過現在您有了一組工具,能更快地實現您的設計。



          Avalon已經就緒

          Avalon Framework, Avalon Excalibur, 和 Avalon LogKit已經準備好讓您使用了。它們已經成熟,只會變得越來越好。 盡管Avalon Phoenix和Avalon Cornerstone正在緊鑼密鼓地開發,但您在它們基礎上寫的代碼將來只需做些不重要的改動就能工作。


          posted on 2006-08-18 20:14 Lansing 閱讀(2389) 評論(0)  編輯  收藏 所屬分類: Java
          <2006年8月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          歡迎探討,努力學習Java哈

          常用鏈接

          留言簿(3)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          Lansing's Download

          Lansing's Link

          我的博客

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 神木县| 正阳县| 昆明市| 思南县| 江阴市| 沂南县| 东莞市| 石门县| 隆回县| 普宁市| 永州市| 新泰市| 顺昌县| 平和县| 裕民县| 东兴市| 长治县| 南阳市| 尼勒克县| 榆中县| 大洼县| 广水市| 呼玛县| 富宁县| 永济市| 龙川县| 江津市| 沁水县| 闽清县| 定陶县| 庐江县| 茶陵县| 景德镇市| 株洲市| 鹤岗市| 娄烦县| 伽师县| 巴青县| 广元市| 宜兴市| 勐海县|