企業應用的Ant模組編譯環境

 作者:Les A. Hazlewood 06/22/2005 翻譯:tetsu 版權聲明:可以任意轉載,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明英文原文地址: http://www.onjava.com/pub/a/onjava/2005/06/22/modularant.html 中文地址: http://www.matrix.org.cn/resource/article/43/43716_Ant.html 關鍵詞: Ant Compile 編譯環境對於今日的Java企業級應用程序來說,越來越難於管理了。堆積如山的代碼,配置文件,以及對第三方的依賴(third-party dependencies)都使得管理編譯環境變得困難。 簡而言之,我們勉強接受那種把所有的源代碼放在一個根目錄下,所有的配置文件放在另一個根目錄下,而第三方類庫也這樣處理的做法。但是企業級編譯環境很少這麼做。今日的企業級Java項目,在結構,功能,以及組織上都很複雜。它們通常都有大量的源代碼和支持資源(屬性文件,圖片,等等。編者注:原文爲supporting artifacts,直譯爲支持物件,但這裏根據上下文意譯爲支持資源較妥)要去管理。有這麼多的東西去管理,當一個開發團隊試圖去建立一個優化的編譯方案時,他們常常感到困惑和挫敗。 如果,不管這個項目有多大,我們的編譯環境都能夠在統一的構架中簡潔地處理我們所有的源代碼,事情是不是會變得好一些呢?本文將展示一個Ant編譯環境的例子,它來自我對多年來的多個項目的經驗的修改。此時此地,它或許不是最好的方案,但是它的確經歷了時間的考驗,也一定會幫助你建立並運行在大多數項目上,不管是大是小。 警告先就一些問題說明一下,這樣你就不會讀完了這篇文章才發現它對你沒有任何價值: · 本文基於對Ant的瞭解。它是針對那些會用並喜歡Ant的讀者的。 · 這裏所說的編譯環境是指模組(modular)和模塊(module),而模塊又是由目錄和子目錄來定義的。(譯者注:模組modular是模塊module的集合。它由多個獨立的模塊構成。)這意味着文件和源代碼被存放在許多不同的目錄中。因此,如果你使用類似Eclipse或IntelliJ Idea這種可以幫你管理類和文件的位置的IDE工具的話,本文對你會更加有益。當然,你也可以使用文本編譯器,但是恐怕你會發現你頻頻地在多棵“目錄樹”上爬上爬下。 概念 首先,讓我們來談及掉隱藏在編譯環境之後的幾個核心概念。它們是模組,層級結構(hierarchical),和資源驅動(artifact-driven)。它們確切的含義又是什麼呢? 模組模組編譯是指圍繞軟件模塊來進行組織的一種編譯方式。一個模塊是一個邏輯的,集合的,功能性單元,對應於系統中的一個特性。對於編譯環境而言,一個模塊表現爲源代碼和配置文件的一個自我包含集合(self-contained collection),這些源代碼和配置文件用來構建表現了模塊所對應的那個命名特性的軟件。它幾乎和你修訂控制系統(RCS:Revision Control System)(例如CVS或者Subversion)中的目錄樹是一一對應的。舉幾個例子:security, administration, wiki, email都可以是一個模塊。 層級結構層級結構編譯是指含有分層模塊的編譯方式。也就是,對於一個模塊,它可能是由更小的,更特定的子模塊(submodule)來構成的。 如果一個模塊含有子模塊,那麼它有責任保證那些子模塊以合適的方式被編譯。隨後,我們會討論例子是如何應用層級結構的概念來建立編譯環境的。 物件驅動物件驅動編譯是指每個存在的模塊(module)或子模塊(submodule),都是爲了產生一個單獨的,可部署的物件。在Java項目中,這些物件主要是.jar,.war,或.ear文件。在其他類型的編譯中,它們通常是二進制可執行文件或動態連接庫(.dll或.so)。編譯環境的例子也是物件驅動的,我們將會討論它是如何創建可部署的物件的。儘管這三個概念都很容易理解,但結合起來用在編譯環境中的話,它們會變得非常強大。現在讓我們來看看編譯環境是如何組織的。 模組結構當有很多要去實現的時候,把問題分解爲若干個小的部分是個很有效的方法。我們需要一個好的分而治之(divide-and-conquer)的技術來幫助我們來管理大量的源碼。在編譯環境中創建編譯模塊是個好方法。 我們通過在應用程序的根目錄下創建一個目錄來創建一個模塊。這個新的目錄成爲這個模塊的基礎。在每個模塊目錄下,我們存放與其相關的文件和源碼。這是一個示例程序的編譯環境,按照模塊來組織: appname/ |-- admin/ |-- core/ |-- db/ |-- lib/ |-- ordermgt/ |-- reports/ |-- web/ |-- build.xml 下面是每個節點的含義: · 除了lib/ 以外的每個目錄都是一個模塊。在這個例子中,admin模塊提供了POJO的實現,它容許某人來管理應用(例如,創建用戶,授權等等)。同樣的,reports模塊中,有能夠產生報告的組件的實現。而core 模塊中是那些在很多或全部模塊中都用到的組件,它們不是真正地和系統的某個功能相聯繫。(例如,StringUtil 類)通常,其他地所有模塊都會依賴核心(core)模塊。其他模塊與admin, reports, 及core模塊一樣:他們有着各自的自包含的系統功能,並與其他模塊區別開來。此外,由於我們的範例應用可以支持基於web的交互,我們還可以有一個web模塊,包含了用以創建一個.war文件所需要的一切內容。 · lib/ 目錄比較特殊。它含有應用程序編譯或運行所需地所有第三方.jars文件。我們把其他模塊所需的所有第三方.jars文件放在這個目錄中,而不是它們自己的模塊中。原因如下: 1. 在一個地方更便於管理對第三方的依賴(third-party dependencies)。可以在一個模塊的build.xml 文件中,利用Ant的 語句來定義改模塊是否使用這些庫文件。 2. 通過排除重複.jars文件的可能性,從而避免了裝載類或API的版本衝突。如果有不止一個模塊使用了一個負責存儲commons-logging.jar文件的Jakarta Commons Logging模塊,會發生什麼情況?假設每個模塊都持有Jakarta Commons Logging模塊的備份,這樣就會有一個潛在的問題――一個模塊所持有的備份和另外一個模塊所持有的版本不同。當應用程序開始運行,只有第一個在classpath上找到的.jar文件被載入以滿足所需,這就潛在地引起了與其他模塊的衝突。我們通過在根目錄下只持有一個.jar文件來避免這種衝突。 3. 對第三方的依賴隨你的源碼改變版本。瀏覽很多項目,會發現,這是你想把你所依賴的庫文件放在CVS上的最重要原因。通過這樣做,你能確保,無論你從CVS上導出的是那個版本或那個分支的軟件,你都能找到第三方類庫的合適版本來支持你的軟件的特定版本。 · 根build.xml 文件是主要的管理文件。它知道爲了編譯每個模塊,什麼文件和目標(target:譯者注,應該是,是Ant中的一個語句)是必須的。然後,由模塊來保證這些物件(artifact)被正確的編譯。 例如,假設一個項目正在編譯,現在是編譯ordermgt 模塊的時候了,根編譯文件(root build file)應該知道,去調用ordermgt/build.xml 文件中一個Ant任務來完成這編譯。而ordermgt/build.xml 文件應該確切的知道要編譯生成ordermgt .jar 文件需要些什麼。而且,如果整個項目被編譯併合併入一個.ear文件,這個根build.xml 文件應該知道如何去構建。根build.xml 文件是怎麼知道要去編譯一個模塊並且以何種順序來編譯的呢?下面是一個Ant XML文件的一部分,它顯示了build.xml文件是如何完成設定的目標的:

<-- Define the modules and the order in which they are executed for any given target. This means _order matters_. Any dependencies that are to be satisfied by one module for another must be declared in the order the dependencies occur. --> Executing "${target}" / target for the core module... Executing "${target}" / target for the admin module... ... 無論根build.xml 文件調用了哪個編譯目標,都由這個template 目標負責以一定的順序傳遞給相應的子模塊。例如,如果我們想要清理整個工程,我們應該只需在工程的根部調用clean 目標即可,然後下面的任務將被執行:

Cleaning all builds" 根build.xml 文件通過直接調用template 目標來間接地實現調用clean 目標,從而保證了所有模塊都被清理。 上面的模塊組織和相關的編譯目標真地使管理源碼和編譯變得更容易了。這種結構有助於你更快,更容易地找到你想要的源碼。而template 目標負責管理任務是如何執行的。 但模塊結構最精彩的部分是這裏:在完成了整個工程的完整編譯後,可以對任何模塊進行獨立的編譯。只要在命令行中切換到該模塊目錄下,並執行: > ant target 然後那個模塊的build.xml 文件就會被執行。你可以在編譯的任何級別下運行任何目標,而且只對該級別進行編譯。 爲什麼這點很重要?因爲它容許你獨立地工作在你的模塊中,並且只對該模塊進行編譯。這樣,每次你對該模塊源碼的修改都不需要你重新編譯整個工程,對於一個巨大的工程而言,這將節省很多時間。 現在,讓我們來看看一個獨立的模塊的內部是如何構造的。 模塊的內容我們按照一般的Java業界習慣來組織模塊的目錄結構,以便管理源碼。儘管有很多不同的習慣,我們的編譯環境中使用一下的目錄結構: modulename |-- build/ |-- etc/ |-- src/ |-- test/ |-- build.xml 下面是每個節點的含義: · build: 這個目錄比較特殊,它是由模塊的編譯而產生的。除了它以外,上面所列出的其他文件和目錄都會被加入修訂控制系統(RCS:Revision Control System)。build目錄中包含編譯過程中所產生的所有文件,從自動生成的XML文件到編譯完成的Java .class文件,以及最終的任何發佈文件(distribution artifacts)(如.war, .jar, .ear,等等)。這使得清理編譯變得很容易,只要刪除這個目錄就好了。 · etc: 這個目錄中存放編譯或運行時(run time)模塊所需的配置文件。大多數時候,你會在這裏找到屬性文件和XML配置文件,例如log4j.properties 或 struts-config.xml。假設有很多文件,他們通常被存放在他們相關組件的子目錄中。例如:etc/spring/, etc/struts/, etc/ejb/, 等等。 · src: 這是你源文件目錄樹的根目錄。這裏除了與包和(或者)路徑相對應的目錄之外,就別無他物了。因此,在這裏你經常會看到com/ 或 net/ 或 org/ 目錄,作爲com.whatever 或 net.something 或 org.mydomain 包的開始。要注意,只有和classpath有一一對應關係的東西才能放在這個目錄中。例如:包目錄,或 .java源文件。 · test: 這個目錄用來存放你的測試類文件。(例如,JUnit測試單元)。從目錄組織的角度出發,這裏最重要的是,這裏的包結構是src 目錄的嚴格鏡像。這很便於管理測試程序,因爲你立即就會知道:moduleroot/test/com/domain/pkg/BusinessObjectTest 是對moduleroot/src/com/domain/pkg/BusinessObject的進行的測試。這個簡單的鏡像技術對於管理大量的code是非常有用的。它使你很容易的找到你的測試程序。 · build.xml: 這個Ant文件知道如何去做每件這個模塊需要做的事情,以完成編譯和分配它所負責的物件。如果當前模塊含有子模塊,那麼它也知道如何去編譯以及按何種順序去編譯那些子模塊。子模塊和編譯順序都是我們很快要解釋的,非常重要的概念。 子模塊子模塊就是另外一個模塊(父模塊)的子集。你或許看過其他的模式――基於Ant的扁平層級:例如,只有一層深度的結構。而我們的編譯結構要走的更遠一些:我們有2層。繼續我們的編譯和子模塊的概念,你將會看到如下的一個編譯層次,模塊和子模塊目錄展開如下: module1/ submodule1.1/ |-- etc/ |-- src/ ... |-- build.xml submodule1.2/ |-- etc/ |-- src/ ... |-- build.xml build.xml module2/ ... OK, 這個看起來有點複雜。我們爲什麼需要這個? 讓我們補充點企業級應用和物件驅動的背景知識,再來回答這個問題。企業級應用程序大部分情況下都是基於客戶/服務器模式的。即便你只是開發一個網頁應用程序,它通常構成一個客戶/服務器MVC應用。這樣,網頁本身就是一個客戶視角,而“服務器”端組件通常是商業POJO,它代替發佈網頁組件執行商業邏輯。儘管它們在一個 .war文件中,但是在主要用於繪製視圖(rendering a view)(客戶端代碼)的代碼和用於處理商業請求(服務器端代碼)的代碼之間有明確的架構分離。至少,在這裏是這樣的! 在傳統的客戶/服務器應用程序中,客戶和服務器的概念更加明顯。那裏有獨立的客戶GUI通過socket和服務器端的商業對象進行通信。 如果對於客戶應用程序我們只需要改動客戶端代碼,對於服務器端程序只改動服務器端源碼,那將是簡潔而優雅的。這兩層也共享一些通用代碼,因此向客戶端和服務器端發送共用的.jar文件也是好主意。我們的編譯環境有能力實現我們的想法。接下來我們會看到子模塊是如何幫我我們實現物件驅動編譯的。 分級結構和編譯物件部署場景(deployment scenario)只描述了物件驅動編譯的表層需求:編譯環境中的每個模塊或子模塊複雜創建一個會被部署到客戶或服務器端的物件。在我們的編譯環境中這個很容易實現,只要把已有的模塊進一步分解爲common, client, 和 server 子模塊就好了。父子關係以及編譯責任委託也促成這種編譯層級結構。 以我們示例程序中的admin 模塊爲例,讓我們看看在展開的目錄樹中這種層級結構是怎樣的: appname/ |-- admin/ |-- common/ |-- etc/ |-- src/ |-- test/ |-- build.xml |-- client/ |-- etc/ |-- src/ |-- test/ |-- build.xml |-- server/ |-- etc/ |-- src/ |-- test/ |-- build.xml |-- build.xml ... 每個子模塊的內容都按照先前定義的構建,但是有些值得注意的不同: admin 模塊沒有通常模塊的那些內容。它只含有子模塊和一個build.xml文件,而且它自己也不會產生任何編譯產物。而是通過先前描述的模板(template)技術來調用common/build.xml, server/build.xml, 和 client/build.xml 文件中的編譯目標來完成編譯工作的。因此,如果你想編譯admin 模塊,你只需要切換到admin目錄下,然後運行Ant: > cd admin/ > ant 這個命令會調用admin的build.xml 文件,其結果是編譯common, server, 和 client 子模塊。在每個子模塊被編譯後,將會產生三個物件: appname-admin-common.jar appname-admin-server.jar appname-admin-client.jar common 和 server .jars 文件可以被部署到服務器端(例如,在一個 .ear文件中),而common and client .jars 文件可以被部署到客戶端(如,在 .war文件的WEB-INF/lib 目錄中)。每個子模塊的目的是什麼呢?它們幫助以簡潔的功能性子集來組織代碼,這些子集會被部署到應用程序的不同層面上。一下是上述三個子模塊通常所含有的內容: · common: 模塊中,所有在客戶和服務器層都會用到的代碼。這通常意味這POJO接口,單元類,等等。 · server:服務器層所需類的實現。它們通常是商業POJO接口的實現,DAO爲EIS連接的實現,等等。 · client: 客戶層所需類的實現,例如Swing GUI對象,EJO遠程接口,等等。 這種子模塊的獨立性(granularity)及其相應的部署物件從4個方面對你產生實質性幫助: 1. 下載時間:你可以確保獨立的客戶端應用程序,例如applet或Java Web Start 程序,只需下載程序運行所需的.jar文件的最小子集。這樣可以確保第一次運行的applet或應用程序能儘快的被下載。 2. 依賴性管理: 通過子模塊的build.xml 文件中Ant的 標識,你可以添加當前子模塊所依賴的其他模塊或子模塊。這樣避免了任何懶惰或意外的使用了開發者不支持的或在運行時不支持的API 。 3. 依賴性順序: 因爲父模塊決定了子模塊的編譯順序,因此你可以重新設定以確保你寫的客戶端代碼依賴於common 代碼,而不依賴於服務器端代碼。同樣,common 代碼不能依賴於服務器或客戶端代碼。如果你沒有這麼做,編譯就會中止,你就會立即的得到警告,你用了你不該使用的類。這聽起來好像是很瑣碎的問題,但是在複雜的工程或那些程序員有着不同的經驗的情況下,這個問題會迅速浮出水面,而且不會在依賴性管理中被注意到。 4. 正如同處理模塊一樣,你也可以通過切換到子模塊的目錄裏並運行以下命令來對一個子模塊進行單獨編譯: > ant 而Ant會只編譯該模塊,節省了時間。 結論模塊和子模塊看起來有點複雜。在這點上它們在你看來好象沒此必要。但請相信我的經驗,它們極大的簡化你如何管理源碼和關聯文件,以及Ant是如何編譯你的產品的。在這裏定義的結構真真正正地使得產品特性和源碼管理在團隊環境下變得更容易了。它剔除了爲了實現全部組織工作所臆想的許多任務,而且一旦建立起來,它是相當透明的。如果你正在開始一項新的客戶/服務器模式的項目,那麼就試着應用它。你會有更多的時間投入到你的程序中,而不用在爲配置管理而但心。特別感謝Transdyn delivers 的Jeremy Haile,他提供了有意義的信息和審閱了本文。

發佈了14 篇原創文章 · 獲贊 2 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章