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月發行的版本非常相似。實際上,主要的區別是對包重新組織,以及將項目劃分為子項目。同樣的設計模式和介面至今依然存在。
什麼是Avalon?
Avalon 是五個子專案的父專案:Framework、Excalibur、LogKit、Phoenix、和Cornerstone。聽到Avalon時,大多數人會聯想到Framework,但Avalon不只包含Framework。 Avalon開始作為Java Apache Server Framework時就包含框架、工具、元件和一個伺服器核心的實現,這些都在一個專案中。因為Avalon的不同部分有不同的成熟程度,發布週期也不同,我們決定將Avalon劃分為前面提到的小項目。這樣做也便於新開發者理解和學習Avalon的不同部分——這在以前幾乎無法辦到。 Framework
Avalon Framework是Avalon大傘下的所有其它項目的基礎。它定義了介面、契約(contracts)和Avalon的預設實作。 Framework將大部分工作置於其中,因此也是最成熟的專案。
Excalibur
Avalon Excalibur是一組伺服器端元件,您可以在自己的專案中使用它們。它包括了池(pooling)的實作、資料庫連接管理和其它一些元件管理的實作。
LogKit
Avalon LogKit是一個高速記錄工具集,Framework、Excalibur、Cornerstone和Phoenix都用到它。它的模型與JDK 1.4 Logging package採用相同的原理,但與JDK 1.2+相容。
Phoenix
Avalon Phoenix是伺服器核心,它管理服務(Service,實作為伺服器端元件,稱為Block)的發布和執行。
Cornerstone
Avalon Cornerstone是一組Block或服務,這些Block可以佈署在Phoenix環境中。這些Block包含了socket管理和Block之間的任務排程。
Scratchpad
Scratchpad並不是一個真正的正式項目,而是那些還沒準備好放入Excalibur中的元件的一個臨時區域。這些組件品質差異較大,它們的API也不保證會不變,直到它們被提升到Excalibur專案為止。
本概述的重點
在這個概述中,我們把重點放在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] 或 parameterize() [Parameterableable 5. () [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 片段中匯出的,看起來是這樣的:
#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 。 "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. } } /*** 構造函數。所有元件都需要一個公共的無參數建構函式*才能成為合法的元件。*/ public final void compose(ComponentManager cmanager) throws ComponentException { if (oid compose(ComponentManager cmanager) throws ComponentException { if ( || 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(Alllegaldisn dis } this.initialized = true; } public final void dispose() { this.disposed = true; this.manager = null; this.dbResource = null; } public final Document getDocument(Principal requestor, int refId) {public final Document getDocument(Principal requestor, int refId) { int (!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 DocumentGuardianComponentManager. void initialize() throws Exception { Logger docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); this.docs.enableLogging( docLogger.childLogger( "repositenable" docger); .childLogger( "security" ) ); DefaultConfiguration pool = new DefaultConfiguration("dbpool"); pool.setValue("main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this.(pool); 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(); "main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this.docs.configure(conf); this.docs = ComponentHandler.getComponentHandler( DatabaseDocumentRepository.class, conf, this.manager, conf, this.manager , null, null); this.guard = ComponentHandler.getComponentHandler( DocumentGuardianComponent.class, null, this.manager, null, null); Logger docLogger = new LogKitLogger( Hierarchy.defaultHierarchy(). .docs.enableLogging( docLogger.childLogger( "repository" ) ); this.guard.enableLogging( docLogger.childLogger( "security" ) ); this.manager.addComponent(DocumentRepository.ROLE, this.agers); thisman.docman. 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可以用以下的格式讀取設定文件,並從中建立系統的元件。
根元素可由您任一元素可由您任一元素可以讓您任一元素指定。您會注意到我們已經定義了一些元件。我們有了熟悉的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 Defaultdration. conf") ; this.manager.setLogger( Hierarchy.getDefaultHierarchy() .getLoggerFor("document") ); this.manager.contextualize( new DefaultContext() ); this.manager.configure( sysConfig ); this.Contextager.initiize(this.); } 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 );} #讓我們來看看使用我們的框架中的是#burburorthandble#burd RoleManager的。首先,Excalibur循環讀入根元素的所有子元素。這包括了所有的"component"元素,但這次Excalibur並不識別元素的名稱,它詢問RoleManager 對這個組件我們將使用什麼角色。如果RoleManager回傳null,那麼該元素和它所有的子元素都被忽略。接下來, Excalibur 從角色名稱中匯出類別名稱。最後的方法是動態地將類別名稱與ComponentSelector的子類型對應。 Excalibur提供了一個 RoleManager的預設實現,它使用一個XML設定檔。標記相當簡單,它把所有您不希望管理員看到的附加資訊隱藏起來的。
為了使用RoleManager,您需要改變容器類別中的"初始化"方法。您將使用設定建構器(configuration builder)透過這個檔案來建構一個Configuration樹。請記住,如果您打算使用一個RoleManager,您必須在呼叫"configure"方法之前呼叫"setRoleManager"方法。為了展示您如何從類別裝入器中取得這個XML文件,我將在下面展示該技巧:DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();Configuration sysConfig = builder.buildFromFile("./conf/systemxconf");Configuration roleConfig = builder.build( this.getClass().getClassLoader() .getResourceAsStream("/org/apache/bizserver/docs/document.roles"));DefaultRoleManager roles = new DefaultRoleget();roles.enableLogging(Hierarchy.Defaultles.enable ().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行程式碼,就要看一下它帶來了什麼好處。我們最後的設定檔可以這樣寫:
正如您所看到的那樣,與前面的文件相比,這個文件的可讀性要強得多。 現在我們可以為系統增加任意數目的元件,而不需要寫更多的程式碼來支援它們。
使用該元件
現在我們已經建立了我們的元件,我們將使用它們。不管元件是如何被初始化和管理的,您存取元件的方法都是一樣的。您必須實作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; /*** 取得守衛的引用並保留對 * ComponentManager 的引用。*/ public void compose(ComponentManager manager) throws Componententager { this (Exception this.manager = manager; myGuard = (Guardian) this.manager.lookup(Guardian.ROLE); } } /*** 這是使用Guardian的方法。*/ public void myMethod() throws SecurityException { this.myGuard.checkPermission(new BasicPermission(" test")); } /*** 擺脫我們的參考*/ 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; /*** 取得守衛的引用並保留對 * ComponentManager 的引用。*/ public void compose(ComponentManager manager) throws ComponentException { if (this.manager == null) { this.manager = manager; } } /*** 這是獲取守護者的方法。*/ 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); } } } /*** 執行程式碼的關鍵部分。*/ 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"); .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中最複雜的怪獸,但它在很少的幾個類別中提供了很多的功能。在簡單的 SingleThreaded 或ThreadSafe兩種管理類型之外,還有一種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正在緊鑼密鼓地開發,但您在它們基礎上寫的程式碼將來只需做些不重要的改動就能工作。
以上是詳細介紹 Avalonjs的詳細內容。更多資訊請關注PHP中文網其他相關文章!