轉述參考自:http://www.cjsdn.net/post/print?bid=29&id=59107
本概述的重點
在這個概述中,我們把重點放在Avalon Framework上,但會介紹足夠的Avalon Excalibur和Avalon LogKit的相關知識,以便讓您能夠起步。我們將通過一個假想的業務服務器來展示如何在實踐中使用Avalon。定義一個完整全面的方法學,或介紹所有子項目的方方面面超出了本概述的范圍。我們將重點放在Avalon Framework上是因為它是所有其它項目的基礎。如果您能理解該框架,您就可以理解任何基於Avalon的項目。對於Avalon中常用的一些編程習慣結構(idiom),您也會逐漸熟悉。將重點放在框架上並涉及Avalon Excalibur和Avalon LogKit項目的另一個原因是它們是正式發布並被支持的。
Avalon可用在哪里?
我被問過好幾次,要求闡明Avalon適合做什么,不適合做什么。Avalon把重點放在服務器端編程和讓以服務器應用為中心的項目的設計和維護變得更容易。Avalon可被描述為一個包含了實現的框架。盡管Avalon的重點是服務器端解決方案,很多人卻發現對普通應用程序來說它也是有用的。Framework、Excalibur、和LogKit中使用的概念很通用,足以在任何項目中應用。將重點更直接放在服務器上的兩個項目是Cornerstone和Phoenix。Framework
1. 一個支持性的或封閉性的結構2. 包含思想的一個基本系統或一種安排
框架這個詞在應用程序中的含義很廣泛。重點放在單一行業的框架被稱為垂直市場框架,例如醫葯系統或通信系統。原因是同樣的框架不能勝任其它行業。具有很好通用性,可用於多個行業的框架被稱為水平市場框架。Avalon是一個水平市場框架。您可以使用Avalon的Framework構建垂直市場框架。用Avalon構建的最有說服力的垂直市場框架的例子是Apache Cocoon出版框架。Apache Cocoon第2版是使用Avalon的Framework、Excalibur和LogKit項目構建的。它利用了Framework中的接口和契約,讓開發者能用更少的時間理解Cocoon是如何工作的。它也有效地利用了Excalibur提供的數據源管理和組件管理代碼,這樣它就不必重新發明輪子了。最后,它使用了LogKit來處理出版框架中所有的日志問題。一旦您理解了隱藏在Avalon Framework后面的原理,您就能理解基於Avalon構建的任何系統。一旦您理解了系統,您將能更快地捕獲因為誤用框架所引起的缺陷。不存在魔術公式
值得一提的是,任何試圖使用某種工具作為成功的魔術公式的做法都是自找麻煩。Avalon也不例外。因為Avalon的Framework是為服務器端解決方案設計的,所以用它來構建圖形用戶界面(GUI)不是什么好主意。Java已經有了一個構建GUI的框架,稱為Swing。盡管您需要考慮Avalon是否適合您的項目,但您還是能從它的原理和設計中學到一些東西。您需要問自己的問題是:"項目將用在哪里?" 如果回答是它將運行在一個服務器環境中,那么Avalon將是一個好的選擇,無論您是要創建一個Java Servlet或一個特殊用途的服務器應用。如果回答是它將運行在一個客戶的機器上,並且與服務器沒有交互,那么可能Avalon不太適合。即使如此,組件模型也是非常靈活,有助於在大型應用程序中對復雜性進行管理。
原理和模式
Avalon整個是基於一些特定設計原理來構建的。最重要的兩個模式是反向控制(Inversion of Control) 和分離考慮(Separation of Concerns)。 Component Oriented Programming、Aspect Oriented Programming和Service Oriented Programming也對Avalon產生了影響。每種程序設計原理都可以寫出數卷的書,但它們都是一些設計思維習慣。反向控制
反向控制(Inversion of Control,IOC)的概念是指組件總是由外部進行管理的。這個短語是由Brian Foote在他的一篇論文中最先使用的。組件所需的一切通過Contexts、Configurations和Loggers的方式賦予組件。實際上,組件生命周期中的每個階段都是由創建組件的代碼所控制的。當您使用這種模式時,就實現了一種組件與您的系統安全交互的方法。IOC與安全性並不等價! IOC提供了一種機制,允許你實現一個可擴展的安全模型。為了讓一個系統真正做到安全,每個組件都必需安全,沒有組件可以修改傳遞給它們的對象的內容,而且所有的交互都必須使用已知的實體。安全性是一個主要問題,IOC是程序員工具庫中的一種工具,用於實現安全性的目標。
分離考慮
您應該從不同的思考方向來看待您的系統,這一思想導致了分離考慮(Separation of Concerns,SOC)模式。 一個例子是從同一個問題空間的不同視角來看一個web服務器。web服務器必需安全、穩定、可管理、可配置並滿足HTTP規范。每種屬性都是一個單獨的考慮范圍。這其中的某些考慮與其它考慮相關,如安全性和穩定性(如果一個服務器不穩定,它就不可能安全)。分離考慮模式又導致了Aspect Oriented Programming (AOP) 。研究者發現許多考慮不能在類或方法的粒度上進行處理。這些考慮被稱為aspect。aspect的例子包括管理對象的生命周期、記日志、處理異常和清理釋放資源等。由於沒有一種穩定的AOP實現,Avalon開發團隊選擇通過提供一些小的接口,然后由組件來實現,從而實現aspect或考慮。
面向組件的編程
面向組件的編程(Component Oriented Programming ,COP)是把系統分割成一些組件或設施的一種思想。每種設施都有一個工作接口和圍繞該接口的契約。這種方式允許容易地更換組件的實例,同時不影響系統其它部分的代碼。面向對象編程(Object Oriented Programming ,OOP)和COP的主要區別在於集成的層次。COP系統的復雜性更容易管理,這得益於類之間更少的相互依賴。這提高了代碼重用的程度。COP的主要好處之一是修改項目代碼的一些部分不會破壞整個系統。另一個好處是可以有某組件的多種實現,並可以在運行時刻進行選擇。
面向服務的編程
面向服務的編程(Service Oriented Programming ,SOP)的思想是把系統划分為由系統提供的一些服務。服務
1. 為其它人執行的工作或職責2. 提供修理或維護的一種設施3. 向公眾提供工具的一種設施
Avalon的 Phoenix把每一種要提供的設施看作是一項服務,由特定接口和相關契約組成。服務的實現被稱為Block。一個服務器程序是由多種服務組成的,認識這一點很重要。以郵件服務器為例,它會有協議處理服務、認證和授權服務、管理服務和核心郵件處理服務等。Avalon的 Cornerstone提供了一些低層的服務,您可以在自己的系統中加以利用。提供的服務包括連接管理、socket管理、參與者/角色管理和調度等。我們在這里介紹到服務是因為它與把我們的假定系統分解為不同設施的過程有關。
分解一個系統
您是如何決定由哪些東西組成一個組件的? 關鍵是定義您的解決方案所需的設施,以便能有效率地進行操作。
我們將使用一個假想的業務服務器來展示如何識別和確定服務與組件。在我們定義了一些系統用到的服務之后,我們將以這些服務中的一個為例,定義該服務所需的不同組件。我的目標是傳遞給您一些概念,這些概念有助於您把您的系統定義成可管理的部分。
系統分析——識別組件
盡管提供一個完整全面的方法學不是本篇的范圍,我還是願意討論幾點問題。我們將從組件和服務的面向實現的定義開始,然后提供一個實踐的定義。組件
一個組件是一個工作接口和該工作接口的實現的組合。使用組件提供了對象間的松耦合,允許改變實現而不影響使用它的代碼。
服務
一個服務由一個或更多的組件組成,提供了一種完整的解決方案。服務的例子包括協議處理器、任務調度器、認證和授權服務等等。
盡管這些定義提供了一個起點,但它並沒有提供一個完整圖景。為了把一個系統(定義為一組設施,組成一個項目)分解為必要的組成部分,我建使用自頂向下的方式。采用這種方式可以在您了解到底有哪些設施之前,避免陷入到細節的泥潭中。確定您的項目范圍
您的項目預期完成什么功能,一開始您總要有個大致的想法。在商業世界里,初始工作說明(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
發現組件
我們將以前面提到的文檔中心服務為例來說明識別合適的組件的過程。為討論方便起見,我們現在來列出文檔中心服務的需求。文檔中心將采用一個數據庫來作為持久存儲,對客戶端授權,在內存中緩存文檔。組件的實踐性定義
當我們談論組件時,您考慮問題的角度應該是:我的服務需要操作哪些設施?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] 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="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保存着所有您需要的組件的引用