Maven 3.0.3 簡單教程

                   Chapter 1. 介紹 Apache Maven

                   Chapter 1. 介紹 Apache Maven

1.1. Maven... 它是什麼?

1.2. 約定優於配置(Convention Over Configuration)

1.3. 一個一般的接口

1.4. 基於Maven插件的全局性重用

1.5. 一個“項目”的概念模型

1.6. Maven是Ant的另一種選擇麼?

1.7. 比較Maven和Ant

1.8. 總結

雖然網絡上有許多Maven的參考文章,但是沒有一篇單獨的,編寫規範的介紹Maven的文字,它需要是一本細心編排的入門指南和參考手冊。我們做的,正是試圖提供這樣的,包含許多使用參考的文字。

                   1.1. Maven... 它是什麼?

如何回答這個問題要看你怎麼看這個問題。 絕大部分Maven用戶都稱Maven是一個"構建工具":一個用來把源代碼構建成可發佈的構件的工具。構建工程師和項目經理會說Maven是一個更復雜的東西:一個項目管理工具。那麼區別是什麼? 像Ant這樣的構建工具僅僅是關注預處理,編譯,打包,測試和分發。像 Maven 這樣的一個項目管理工具提供了構建工具所提供功能的超集。除了提供構建的功能,Maven還可以生成報告,生成Web站點,並且幫助推動工作團 隊成員間的交流。

一個更正式的 Apache Maven 的定義: Maven是一個項目管理工具,它包含了一個項目對象模型 (Project Object Model),一組標準集合,一個項目生命週期(Project Lifecycle),一個依賴管理系統(Dependency Management System),和用來運行定義在生命週期階段(phase)中插件(plugin)目標(goal)的邏輯。 當你使用Maven的時候,你用一個明確定義的項目對象模型來描述你的項目,然後 Maven可以應用橫切的邏輯,這些邏輯來自一組共享的(或者自定義的)插件。

別讓Maven是一個"項目管理"工具的事實嚇跑你。如果你只是在找一個構建工具,Maven能做這個工作。事實上,本書的一些章節將會涉及使用Maven來構建和分發你的項目。

                   1.2. 約定優於配置(Convention Over Configuration)

約定優於配置是一個簡單的概念。 系統,類庫,框架應該假定合理的默認值,而非要求提供不必要的配置。流行的框架如 Ruby on Rails 和 EJB3 已經開始堅持這些原則,以對像原始的 EJB 2.1 規範那樣的框架的配置複雜度做出反應。一個約定優於配置的例子就像EJB3 持久化,將一個 特殊的Bean持久化,你所需要做的只是將這個類標註爲 @Entity 。 框架將會假定表名和列名是基於類名和屬性名。系統也提供了一些鉤子,當有需要的時候你可以重寫這些名字,但是,在大部分情況下,你會發現使用框架提供的默認值會讓你的項目運行的更快。

Maven通過給項目提供明智的默認行爲來融合這個概念。 在沒有自定義的情況下,源代碼假定是在${basedir}/src/main/java,資源文件假定是在 ${basedir}/src/main/resources 。測試代碼假定是在${basedir}/src/test 。項目假定會產生一個 JAR 文件。Maven 假定你想要把編譯好的字節碼放到${basedir}/target/classes 並且在 ${basedir}/target 創建一個可分發的 JAR 文件。雖然這看起來無關緊要,但是想想大部分基於 Ant 的構建必須爲每個子項目定義這些目錄。 Maven 對約定優於配置的應用不僅僅是簡單的目錄位置,Maven 的核心插件使用了一組通用的約定,以用來編譯源代碼,打包可分發的構件,生成 web 站點,還有許多其他的過程。 Maven 的力量來自它的"武斷",它有一個定義好的生命週期和一組知道如何構建和裝配軟件的通用插件。如果你遵循這些約定,Maven 只需要幾乎爲零的工作——僅僅是將你的源代碼放到正確的目錄,Maven 將會幫你處理剩下的事情。

使用“遵循約定優於配置”系統的一個副作用是用戶可能會覺得他們被強迫使用一種特殊的方法。當然 Maven 有一些核心觀點不應該被懷疑,但是其實很多默認行爲還是可配置的。 例如項目源碼的資源文件的位置可以被自定義,JAR 文件的名字可以被自定義,在開發自定義插件的時候,幾乎任何行爲可以被裁剪以滿足你特定的環境需求。如果你不想遵循約定,Maven 也會允許你自定義默認值來適應你的需求。

                   1.3. 一個一般的接口

在 Maven 爲構建軟件提供一個一般的接口之前,每個單獨的項目都專門有人來管理一個完全自定義的構建系統。開發人員必須在開發軟件之外去學習每個他們要參與的新項目的構建系統的特點。在2001年,構建一個項目如 Turbine 和構建另外一個項目如 Tomcat,兩者方法是完全不同的。如果一個新的進行靜態源碼分析的源碼分析工具面世了,或者如果有人開發了一個新的單元測試框架,每個人都必須放下手頭的工作去想辦法使這個新東西適應每個項目的自定義構建環境。如何運行單元測試?世界上有一千種不同的答案。構建環境由無數無休止的關於工具和構建程序的爭論所描述刻畫。Maven 之前的時代是低效率的時代,是“構建工程師”的時代。

現 在,大部分開源開發者已經或者正在使用 Maven 來管理他們新的軟件項目。這種轉變不僅僅是開發人員從一種構建工具轉移到另外一種構建工具,更是開發人員開始爲他們的項目採用一種一般的接口。隨着軟件系統變得越來越模塊化,構建系統變得更復雜,而項目的數量更是如火箭般飛速上升。在 Maven 之前,當你想要從 Subversion 簽出一個項目如 Apache ActiveMQ 或 Apache ServiceMix ,然後從源碼進行構建,你需要爲每個項目留出一個小時來理解給它的構建系統。這個項目需要構建什麼?需要現在什麼類庫?把類庫放哪裏?構建中我該運行什麼目標?最好的情況下,理解一個新項目的構建需要幾分鐘,最壞的情況下(例如 Jakarta項目的舊的 Servlet API 實現),一個項目的構建特別的困難,以至於花了幾個小時以後,新的貢獻者也只能編輯源碼和編譯項目。現在,你只要簽出源碼,然後運行: mvn install 。雖然 Maven 有很多優點,包括依賴管理和通過插件重用一般的構建邏輯,但它成功的最核心原因是它定義了構建軟件的一般的接口。每當你看到一個使用 Maven 的項目如 Apache Wicket ,你就可以假設你能簽出它的源碼然後使用 mvn install 構建它,沒什麼好爭論的。你知道點火開關在哪裏,你知道油門在右邊,剎車在左邊。

                   1.4. 基於Maven插件的全局性重用

Maven 的核心其實不做什麼實際的事情,除了解析一些 XML 文檔,管理生命週期與插件之外,它什麼也不懂。Maven 被設計成將主要的職責委派給一組 Maven 插件,這些插件可以影響 Maven 生命週期,提供對目標的訪問。絕大多數 Maven 的動作發生於Maven 插件的目標,如編譯源碼,打包二進制代碼,發佈站點和其它構建任務。你從 Apache 下載的 Maven 不知道如何打包 WAR 文件,也不知道如何運行單元測試,Maven 大部分的智能是由插件實現的,而插件從 Maven 倉庫獲得。事實上,第一次你用全新的Maven 安裝運行諸如 mvn install 命令的時候,它會從中央 Maven 倉庫下載大部分核心 Maven 插件。這不僅僅是一個最小化 Maven分發包大小的技巧,這種方式更能讓你升級插件以給你項目的構建提高能力。Maven 從遠程倉庫獲取依賴和插件的這一事實允許了構建邏輯的全局性重用。

Maven Surefire 插件是負責運行單元測試的插件。從版本 1.0 發展到目前廣泛使用的在 JUnit 基礎上增加了 TestNG 測試框架支持的版本。這種發展並沒有破壞向後兼容性,如果你使用之前 Surefire 插件編譯運行你的 JUnit 3 單元測試,然後你升級到了最新版本的Surefire 插件,你的測試仍然能成功運行。但是,我們又獲得了新的功能,如果你想使用 TestNG 運行單元測試,那麼感謝 Surefire 插件的維護者,你已經可以使用 TestNG 了。你也能運行支持註解的 JUnit 4 單元測試。不用升級 Maven 安裝或者新裝任何軟件,你就能獲得這些功能。更重要的是,除了 POM 中一個插件的版本號,你不需要更改你項目的任何東西。

這種機制不僅僅適用於 Surefire 插件,項目使用 Compiler 插件進行編譯,通過 Jar 插件變成 JAR 文件,還有一些插件生成報告,運行JRuby 和 Groovy 的代碼,以及一些用來向遠程服務器發佈站點的插件。Maven 將一般的構建任務抽象成插件,同時這些插件得到了很好的維護以及全局的共享,你不需要從頭開始自定義你項目的構建系統然後提供支持。你完全可以從 Maven 插件獲益,這些插件有人維護,可以從遠程倉庫下載到。這就是基於 Maven 插件的全局性重用。

                   1.5. 一個“項目”的概念模型

Maven 維護了一個項目的模型,你不僅僅需要把源碼編譯成字節碼,你還需要開發軟件項目的描述信息,爲項目指定一組唯一的座標。你要描述項目的的屬性。項目的許可證是什麼?誰開發這個項目,爲這個項目做貢獻?這個項目依賴於其它什麼項目沒有?Maven不僅僅是一個“構建工具”,它不僅僅是在類似於 make 和 Ant 的工具的基礎上的改進,它是包含了一組關於軟件項目和軟件開發的語義規則的平臺。這個基於每一個項目定義的模型實現瞭如下特徵:

依賴管理

由於項目是根據一個包含組標識符,構件標識符和版本的唯一的座標定義的。項目間可以使用這些座標來聲明依賴。

遠程倉庫

和項目依賴相關的,我們可以使用定義在項目對象模型(POM)中的座標來創建 Maven 構件的倉庫。

全局性構建邏輯重用

插件被編寫成和項目模型對象(POM)一起工作,它們沒有被設計成操作某一個已知位置的特定文件。一切都被抽象到模型中,插件配置和自定義行爲都在模型中進行。

工具可移植性/集成

像 Eclipse,NetBeans,和 InteliJ 這樣的工具現在有共同的地方來找到項目的信息。在 Maven 出現之前,每個 IDE 都有不同的方法來存儲實際上是自定義項目對象模型(POM)的信息。Maven 標準化了這種描述,而雖然每個 IDE 仍然繼續維護它的自定義項目文件,但這些文件現在可以很容易的由模型生成。

便於搜索和過濾構件

像 Nexus 這樣的工具允許你使用存儲在 POM 中的信息對倉庫中的內容進行索引和搜索。

Maven 爲軟件項目的語義一致性描述的開端提供了一個基礎。

                   1.6. Maven是Ant的另一種選擇麼?

當然,Maven 是 Ant 的另一種選擇,但是 Apache Ant 繼續是一個偉大的,被廣泛使用的工具。它已經是多年以來 Java 構建的統治者,而你很容易的在你項目的 Maven 構建中集成 Ant 構建腳本。這是 Maven 項目一種很常見的使用模式。而另一方面,隨着越來越多的開源項目轉移到 Maven 用它作爲項目管理平臺,開發人員開始意識到 Maven 不僅僅簡化了構建管理任務,它也幫助鼓勵開發人員的軟件項目使用通用的接口。Maven 不僅僅是一個工具,它更是一個平臺,當你只是將 Maven 考慮成 Ant 的另一種選擇的時候,你是在比較蘋果和橘子。“Maven”包含了很多構建工具以外的東西。

有 一個核心觀點使得所有的關於 Maven 和. Ant, Maven 和 Buildr, Maven 和 Grandle 的爭論變得無關緊要。Maven並不是完全根據你構建系統的機制來定義的,它不是爲你構建的不同任務編寫腳本,它提倡一組標註,一個一般的接口,一個生命週期,一個標準的倉庫格式,一個標準的目錄佈局,等等。它當然也不太在意 POM 的格式正好是 XML 還是 YAML 還是 Ruby。它比這些大得多,Maven 涉及的比構建工具本身多得多。當本書討論 Maven 的時候,它也設計到支持 Maven 的軟件,系統和標準。Buildr,Ivy,Gradle,所有這些工具都和 Maven 幫助創建的倉庫格式交互,而你可以很容易的使用如 Nexus 這樣的工具來支持一個完全由 Buildr 編寫的構建。Nexus 將在本書後面介紹。

雖然 Maven 是很多類似工具的另一個選擇?但社區需要向前發展,就要看清楚技術是資本經濟中不友好的競爭者之間持續的、零和的遊戲。這可能是大企業之前相互關聯的方式,但是和開源社區的工作方式沒太大關係。“誰是勝利者?Ant 還是 Maven”這個大標題沒什麼建設性意義。如果你非要我們來回答這個問題,我們會很明確的說作爲構建的基本技術,Maven 是 Ant 的更好選擇;同時,Maven 的邊界在持續的移動,Maven 的社區也在持續的是試圖找到新的方法,使其更通用,互操作性更好,更易協同工作。Maven 的核心財產是聲明性構建,依賴管理,倉庫管理,基於插件的高度和重用,但是當前,和開源社區相互協作以降低”企業級構建“的低效率這個目標來比,這些想法的特定實現沒那麼重要。

                   1.7. 比較Maven和Ant

雖然上一節應該已經讓你確信本書的作者沒有興趣挑起 Apache Ant 和 Apache Maven 之間的爭執,但是我們認識到許多組織必須在Apache Ant 和 Apache Maven 之間做一個選擇。本節我們對比一下這兩個工具。

Ant 在構建過程方面十分優秀,它是一個基於任務和依賴的構建系統。每個任務包含一組由 XML 編碼的指令。有 copy 任務和javac 任務,以及 jar 任務。在你使用 Ant 的時候,你爲 Ant 提供特定的指令以編譯和打包你的輸出。看下面的例子,一個簡單的build.xml 文件:

Example 1.1. 一個簡單的 Ant build.xml 文件

<project name="my-project" default="dist" basedir=".">

    <description>

        simple example build file

    </description>

  <!-- set global properties for this build -->

  <property name="src" location="src/main/java"/>

  <property name="build" location="target/classes"/>

  <property name="dist"  location="target"/>

 

  <target name="init">

    <!-- Create the time stamp -->

    <tstamp/>

    <!-- Create the build directory structure used by compile -->

    <mkdir dir="${build}"/>

  </target>

 

  <target name="compile" depends="init"

        description="compile the source " >

    <!-- Compile the java code from ${src} into ${build} -->

    <javac srcdir="${src}" destdir="${build}"/>

  </target>

 

  <target name="dist" depends="compile"

        description="generate the distribution" >

    <!-- Create the distribution directory -->

    <mkdir dir="${dist}/lib"/>

 

    <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->

    <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>

  </target>

 

  <target name="clean"

        description="clean up" >

    <!-- Delete the ${build} and ${dist} directory trees -->

    <delete dir="${build}"/>

    <delete dir="${dist}"/>

  </target>

</project>

 

在這個簡單的 Ant 例子中,你能看到,你需要明確的告訴 Ant 你想讓它做什麼。有一個包含 javac 任務的編譯目標用來將src/main/java 的源碼編譯至 target/classes 目錄。你必須明確告訴 Ant 你的源碼在哪裏,結果字節碼你想存儲在哪裏,如何將這些字節碼打包成 JAR 文件。雖然最近有些進展以幫助 Ant 減少程序,但一個開發者對 Ant 的感受是用 XML 編寫程序語言。

用 Maven 樣例與之前的 Ant 樣例做個比較。在 Maven 中,要從 Java 源碼創建一個 JAR 文件,你只需要創建一個簡單的 pom.xml,將你的源碼放在 ${basedir}/src/main/java ,然後從命令行運行 mvn install。下面的樣例 Maven pom.xml 文件能完成和之前Ant 樣例所做的同樣的事情。

Example 1.2. 一個簡單的 Maven pom.xml

<project>

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.sonatype.mavenbook</groupId>

  <artifactId>my-project</artifactId>

  <version>1.0</version>

</project>

 

這就是你 pom.xml 的全部。從命令行運行 mvn install 會處理資源文件,編譯源代碼,運行單元測試,創建一個 JAR ,然後把這個JAR 安裝到本地倉庫以爲其它項目提供重用性。不用做任何修改,你可以運行 mvn site ,然後在 target/site 目錄找到一個index.html 文件,這個文件鏈接了 JavaDoc 和一些關於源代碼的報告。

誠然,這是一個最簡單的樣例項目。一個只包含源代碼並且生成一個 JAR 的項目。一個遵循 Maven 的約定,不需要任何依賴和定製的項目。如果我們想要定製行爲,我們的 pom.xml 的大小將會增加,在最大的項目中,你能看到一個非常複雜的 Maven POM 的集合,它們包含了大量的插件定製和依賴聲明。但是,雖然你項目的 POM 文件變得增大,它們包含的信息與一個差不多大小的基於Ant 項目的構建文件的信息是完全不同的。Maven POM 包含聲明:“這是一個 JAR 項目”,“源代碼在 src/main/java 目錄”。Ant構建文件包含顯式的指令:“這是一個項目”,“源代碼在 src/main/java ”,“針對這個目錄運行 javac ”,“把結果放到target/classes ”,“從……創建一個 JAR ”,等等。Ant 必須的過程必須是顯式的,而 Maven 有一些“內置”的東西使它知道源代碼在哪裏,如何處理它們。

該例中 Ant 和 Maven 的區別是:

Apache Ant

l     Ant 沒有正式的約定如一個一般項目的目錄結構,你必須明確的告訴 Ant 哪裏去找源代碼,哪裏放置輸出。隨着時間的推移,非正式的約定出現了,但是它們還沒有在產品中模式化。

l     Ant 是程序化的,你必須明確的告訴 Ant 做什麼,什麼時候做。你必須告訴它去編譯,然後複製,然後壓縮。

l     Ant 沒有生命週期,你必須定義目標和目標之間的依賴。你必須手工爲每個目標附上一個任務序列。

Apache Maven

l     Maven 擁有約定,因爲你遵循了約定,它已經知道你的源代碼在哪裏。它把字節碼放到 target/classes ,然後在 target 生成一個 JAR 文件。

l     Maven 是聲明式的。你需要做的只是創建一個 pom.xml 文件然後將源代碼放到默認的目錄。Maven 會幫你處理其它的事情。

l     Maven 有一個生命週期,當你運行 mvn install 的時候被調用。這條命令告訴 Maven 執行一系列的有序的步驟,直到到達你指定的生命週期。遍歷生命週期旅途中的一個影響就是,Maven 運行了許多默認的插件目標,這些目標完成了像編譯和創建一個 JAR 文件這樣的工作。

Maven 以插件的形式爲一些一般的項目任務提供了內置的智能。如果你想要編寫運行單元測試,你需要做的只是編寫測試然後放到${basedir}/src/test/java ,添加一個對於 TestNG 或者 JUnit 的測試範圍依賴,然後運行 mvn test 。如果你想要部署一個web 應用而非 JAR ,你需要做的是改變你的項目類型爲 war ,然後把你文檔根目錄置爲 ${basedir}/src/main/webapp 。當然,你可以用 Ant 做這些事情,但是你將需要從零開始寫這些指令。使用 Ant ,你首先需要確定 JUnit JAR 文件應該放在哪裏,然後你需要創建一個包含這個 JUnit JAR 文件的 classpath ,然後告訴 Ant 它應該從哪裏去找測試源代碼,編寫一個目標來編譯測試源代碼爲字節碼,使用 JUnit 來執行單元測試。

沒有諸如 antlibs 和 lvy 等技術的支持(即使有了這些支持技術),Ant 給人感覺是自定義的程序化構建。項目中一組高效的堅持約定的 Maven POM ,相對於 Ant 的配置文件,只有很少的 XML 。Maven 的另一個優點是它依靠廣泛公用的 Maven 插件。所有人使用Maven Surefire 插件來運行單元測試,如果有人添加了一些針對新的測試框架的支持,你可以僅僅通過在你項目的 POM 中升級某個特定插件的版本來獲得新的功能。

使用 Maven 還是 Ant 的決定不是非此即彼的,Ant 在複雜的構建中還有它的位置。如果你目前的構建包含一些高度自定義的過程,或者你已經寫了一些 Ant 腳本通過一種明確的方法完成一個明確的過程,而這種過程不適合 Maven 標準,你仍然可以在 Maven 中用這些腳本。作爲一個 Maven 的核心插件, Ant 還是可用的。自定義的插件可以用 Ant 來實現,Maven 項目可以配置成在生命週期中運行 Ant 的腳本。

                   1.8. 總結

我們刻意的使這篇介紹保持得簡短。 我們略述了一些Maven定義的要點,它們合起來是什麼,它是基於什麼改進的,同時介紹了其它構建工具。下一章將深入一個簡單的項目,看 Maven 如何能夠通過最小數量的配置來執行典型的任務。

                   Chapter 2. 安裝和運行Maven

2.1. 驗證你的Java安裝

2.2. 下載Maven

2.3. 安裝Maven

2.3.1. 在Mac OSX上安裝Maven

2.3.2. 在Microsoft Windows上安裝Maven

2.3.3. 在Linux上安裝Maven

2.3.4. 在FreeBSD或OpenBSD上安裝Maven

2.4. 驗證Maven安裝

2.5. Maven安裝細節

2.5.1. 用戶相關配置和倉庫

2.5.2. 升級Maven

2.6. 獲得Maven幫助

2.7. 使用Maven Help插件

2.7.1. 描述一個Maven插件

2.8. 關於Apache軟件許可證

本章包含了在許多不同平臺上安裝Maven的詳細指令。我們會介紹得儘可能詳細以減少安裝過程中可能出現的問題,而不去假設你已經熟悉安裝軟件和設置環境變量。本章的唯一前提是你已經安裝了恰當的JDK。如果你僅僅對安裝感興趣,讀完下載Maven安裝Maven後,你就可以直接去看本書其它的部分。如果你對Maven安裝的細節感興趣,本章將會給你提供一個簡要的介紹,以及Apache軟件許可證,版本2.0的介紹。

                   2.1. 驗證你的Java安裝

儘管Maven可以運行在Java 1.4上,但本書假設你在至少Java 5上運行。儘管使用你操作系統上最新的穩定版本的JDK。本書的例子在Java 5或者Java 6上都能運行。

java -version

java version "1.6.0_02"

Java(TM) SE Runtime Environment (build 1.6.0_02-b06)

Java HotSpot(TM) Client VM (build 1.6.0_02-b06, mixed mode, sharing)

Maven能在所有的驗證過的JavaTM 兼容的JDK上工作,也包括一些未被驗證JDK實現。本書的所有樣例是基於Sun官方的JDK編寫和測試的。如果你正在使用Linux,你可能需要自己下載Sun的JDK,並且確定JDK的版本(運行 java -version)。目前Sun已經將Java開源,因此這種情況將來有希望得到改進,將來很有可能Sun JRE和JDK會被默認安裝在Linux上。但直到現在爲止,你還是需要自己去下載。

                   2.2. 下載Maven

你可以從Apache Maven項目的web站點下載Maven:http://maven.apache.org/download.html.

當你下載Maven的時候,確認你選擇了最新版本的Apache Maven。本書書寫的時候最新版本是Maven 2.0.9 。如果你不熟悉Apache軟件許可證,你需要在使用產品之前去熟悉該許可證的條款。更多的關於Apache軟件許可證的信息可以Section 2.8, “關於Apache軟件許可證”找到。

                   2.3. 安裝Maven

操作系統之間有很大的區別,像Mac OSX 和微軟的Windows,而且不同版本Windows之間也有微妙的差別。幸運的是,在所有操作系統上安裝Maven的過程,相對來說還是比較直接的。下面的小節概括了在許多操作系統上安裝Maven的最佳實踐。

                        2.3.1. 在Mac OSX上安裝Maven

你可以從http://maven.apache.org/download.html下載Maven的二進制版本。下載最新的,下載格式最方便你使用的版本。找個地方存放它,並把存檔文件解開。如果你把存檔文件解壓到 /usr/local/maven-2.0.9 ;你可能會需要創建一個符號鏈接,那樣就能更容易使用,當你升級Maven的時候也不再需要改變環境變量。

/usr/local % ln -s maven-2.0.9 maven

/usr/local % export M2_HOME=/usr/local/maven

/usr/local % export PATH=${M2_HOME}/bin:${PATH}

將Maven安裝好後,你還需要做一些事情以確保它正確工作。你需要將它的 bin 目錄(該例中爲 /usr/local/maven/bin)添加到你的命令行路徑下。你還需要設置 M2_HOME 環境變量,其對應值爲Maven的根目錄(該例中爲 /usr/local/maven)。

Note

在OSX Tiger和OSX Leopard上安裝指令是相同的。有報告稱Maven 2.0.6正和XCode的預覽版本一起發佈。如果你安裝了XCode,從命令行運行 mvn 檢查一下它是否可用。 XCode把Maven安裝在了/usr/share/maven。我們強烈建議安裝最新版本的Maven 2.0.9,因爲隨着Maven 2.0.9的發佈很多bug被修正了,還有很多改進。

你還需要把 M2_HOME 和 PATH 寫到一個腳本里,每次登陸的時候運行這個腳本。把下面的幾行加入到 .bash_login。

export M2_HOME=/usr/local/maven

export PATH=${M2_HOME}/bin:${PATH}

一旦你把這幾行加入到你的環境中,你就可以在命令行運行Maven了。

Note

這些安裝指令假設你正運行bash。

                        2.3.2. 在Microsoft Windows上安裝Maven

在Windows上安裝Maven和在Mac OSX上安裝Maven十分類似,最主要的區別在於安裝位置和設置環境變量。在這裏假設Maven安裝目錄是 c:/Program Files/maven-2.0.9 ,但是,只要你設置的正確的環境變量,把Maven安裝到其它目錄也一樣。當你把Maven解壓到安裝目錄後,你需要設置兩個環境變量——PATH和M2_M2_HOME。設置這兩個環境變量,鍵入下面的命令:

C:/Users/tobrien > set M2_HOME=c:/Program Files/maven-2.0.9

C:/Users/tobrien > set PATH=%PATH%;%M2_HOME%/bin

在命令行設置環境變量後,你可以在當前會話使用Maven,但是,除非你通過控制面板把它們加入系統變量,你將需要每次登陸系統的時候運行這兩行命令。你應該在Microsoft Windows中通過控制面板修改這兩個變量。

                        2.3.3. 在Linux上安裝Maven

遵循Section 2.3.1, “在Mac OSX上安裝Maven”的步驟,在Linux機器上安裝Maven。

                        2.3.4. 在FreeBSD或OpenBSD上安裝Maven

遵循Section 2.3.1, “在Mac OSX上安裝Maven”的步驟,在FreeBSD或者OpenBSD機器上安裝Maven。T

                   2.4. 驗證Maven安裝

當Maven安裝完成後,你可以檢查一下它是否真得裝好了,通過在命令行運行 mvn -v。如果Maven裝好了,你應該能看到類似於下面的輸出。

mvn -v

Maven 2.0.9

如果你看到了這樣的輸出,那麼Maven已經成功安裝了。如果你看不到,而且你的操作系統找不到 mvn 命令,那麼確認一下PATH和M2_HOME環境變量是否已經正確設置了。

                   2.5. Maven安裝細節

Maven的下載文件只有大概1.5 MiB,它能達到如此苗條的大小是因爲Maven的內核被設計成根據需要從遠程倉庫獲取插件和依賴。當你開始使用Maven,它會開始下載插件到本地倉庫中,就像Section 2.5.1, “用戶相關配置和倉庫”所描述的那樣。對此你可能比較好奇,讓我們先很快的看一下Maven的安裝目錄是什麼樣子。

/usr/local/maven $ ls -p1

LICENSE.txt

NOTICE.txt

README.txt

bin/

boot/

conf/

lib/

LICENSE.txt 包含了Apache Maven的軟件許可證。Section 2.8, “關於Apache軟件許可證”會詳細描述該許可證。NOTICE.txt 包含了一些Maven依賴的類庫所需要的通告及權限。README.txt包含了一些安裝指令。 bin/目錄包含了運行Maven的mvn腳本。 boot/目錄包含了一個負責創建Maven運行所需要的類裝載器的JAR文件(classwords-1.1.jar)。 conf/ 目錄包含了一個全局的settings.xml文件,該文件用來自定義你機器上Maven的一些行爲。如果你需要自定義Maven,更通常的做法是覆寫 ~/.m2目錄下的settings.xml文件,每個用戶都有對應的這個目錄。lib/ 目錄有了一個包含Maven核心的JAR文件(maven-2.0.9-uber.jar)。

                        2.5.1. 用戶相關配置和倉庫

當你不再僅僅滿足於使用Maven,還想擴展它的時候,你會注意到Maven創建了一些本地的用戶相關的文件,還有在你home目錄的本地倉庫。在~/.m2目錄下有:

~/.m2/settings.xml

該文件包含了用戶相關的認證,倉庫和其它信息的配置,用來自定義Maven的行爲。

~/.m2/repository/

該目錄是你本地的倉庫。當你從遠程Maven倉庫下載依賴的時候,Maven在你本地倉庫存儲了這個依賴的一個副本。

Note

在Unix(和OSX)上,可以用 ~ 符號來表示你的home目錄,(如~/bin表示/home/tobrien/bin)。在Windows上,我們仍然使用 ~ 來表示你的home目錄。在Windows XP上,你的home目錄是 C:/Documents and Settings/tobrien,在Windows Vista上,你的home目錄是 C:/Users/tobrien。從現在開始,你應該能夠理解這種路徑表示,並翻譯成你操作系統上的對應路徑。

                        2.5.2. 升級Maven

如果你遵循Section 2.3.1, “在Mac OSX上安裝Maven”Section 2.3.3, “在Linux上安裝Maven”,在Mac OSX或者Unix機器上安裝了Maven。那麼把Maven升級成較新的版本是很容易的事情。只要在當前版本Maven(/usr/local/maven-2.0.9)旁邊安裝新版本的Maven(/usr/local/maven-2.future),然後將符號鏈接 /usr/local/maven 從 /usr/local/maven-2.0.9 改成/usr/local/maven-2.future即可。你已經將M2_HOME環境變量指向了 /usr/local/maven,因此你不需要更改任何環境變量。

如果你在Windows上安裝了Maven,將Maven解壓到 c:/Program Files/maven-2.future,然後更新你的M2_HOME環境變量。

                   2.6. 獲得Maven幫助

雖然本書的目的是作爲一本全面的參考手冊,但是仍然會有一些主題我們會不小心遺漏,一些特殊情況和技巧我們也覆蓋不到。Maven的核心十分簡單,它所做的工作其實都交給插件了。插件太多了,以至於不可能在一本書上全部覆蓋。你將會碰到一些本書沒有涉及的問題,碰到這種情況,我們建議你在下面的地址去尋找答 案。

http://maven.apache.org

你首先應該看看這裏,Maven的web站點包含了豐富的信息及文檔。每個插件都有幾頁的文檔,這裏還有一系列“快速開始”的文檔,它們是本書內容的十分有 幫助的補充。雖然Maven站點包含了大量信息,它同時也可能讓你迷惑沮喪。那裏提供了一個自定義的Google搜索框,以用來搜索已有的Maven站點 信息,它能比通用的Google搜索提供更好的結果。

Maven User Mailing List

Maven 用戶郵件列表是用戶問問題的地方。在你問問題之前,你可以先搜索一下之前的討論,有可能找到相關問題。問一個已經問過的問題,而不先查一下該問題是否存在了,這種形式不太好。有很多有用的郵件列表歸檔瀏覽器,我們發現Nabble最有用。你可以在這裏瀏覽郵件列表:http://www.nabble.com/Maven---Users-f178.html。你也可以按照這裏的指令來加入用戶郵件列表:http://maven.apache.org/mail-lists.html

http://www.sonatype.com

Sonatype維護了一個本書的在線副本,以及其它Maven相關的指南。

Note

除 去一些專門的Maven貢獻者所做的十分優秀的工作,Maven web站點組織的比較糟糕,有很多不完整的,甚至有時候有些誤導人的文檔片段。在整個Maven社區裏,插件文檔的一般標準十分缺乏,一些插件的文檔十分的豐富,但是另外一些連基本的使用命令都沒有。通常你最好是在用戶郵件列表裏面去搜索下解決方案。

                   2.7. 使用Maven Help插件

本書中,我們將會介紹Maven插件,講述Maven項目對象模型(POM)文件,settings文件,還有profile。有些時候,你需要一個工具來幫助你理解一些Maven使用的模型,以及某個插件有什麼可用的目標。Maven Help插件能讓你列出活動的Maven Profile,顯示一個實際POM(effective POM),打印實際settings(effective settings),或者列出Maven插件的屬性。

Note

如果想看一下POM和插件的概念性介紹,參照第三章:一個簡單的Maven項目。

Maven Help 插件有四個目標。前三個目標是—— active-profiles, effective-pom 和 effective-settings —— 描述一個特定的項目,它們必須在項目的目錄下運行。最後一個目標—— describe ——相對比較複雜,展示某個插件或者插件目標的相關信息。

help:active-profiles

列出當前構建中活動的Profile(項目的,用戶的,全局的)。

help:effective-pom

顯示當前構建的實際POM,包含活動的Profile。

help:effective-settings

打印出項目的實際settings, 包括從全局的settings和用戶級別settings繼承的配置。

help:describe

描述插件的屬性。它不需要在項目目錄下運行。但是你必須提供你想要描述插件的 groupId 和 artifactId。

                        2.7.1. 描述一個Maven插件

一旦你開始使用Maven,你會花很多時間去試圖獲得Maven插件的信息:插件如何工作?配置參數是什麼?目標是什麼?你會經常使用help:describe 目標來獲取這些信息。通過 plugin 參數你可以指定你想要研究哪個插件,你可以傳入插件的前綴(如 help 插件就是 maven-help-plugin),或者可以是 groupId:artifact[:version] ,這裏 version 是可選的。比如,下面的命令使用help 插件的 describe 目標來輸出 Maven Help 插件的信息。

mvn help:describe -Dplugin=help

...

Group Id:  org.apache.maven.plugins

Artifact Id: maven-help-plugin

Version:     2.0.1

Goal Prefix: help

Description:

 

The Maven Help plugin provides goals aimed at helping to make sense out of

    the build environment. It includes the ability to view the effective

    POM and settings files, after inheritance and active profiles

    have been applied, as well as a describe a particular plugin goal to give

    usage information.

...

通過設置 plugin 參數來運行 describe 目標,輸出爲該插件的Maven座標,目標前綴,和該插件的一個簡要介紹。儘管這些信息非常有幫助,你通常還是需要了解更多的詳情。如果你想要 Help 插件輸出完整的帶有參數的目標列表,只要運行帶有參數 full 的help:describe 目標就可以了,像這樣:

mvn help:describe -Dplugin=help -Dfull

...

Group Id:  org.apache.maven.plugins

Artifact Id: maven-help-plugin

Version:     2.0.1

Goal Prefix: help

Description:

 

The Maven Help plugin provides goals aimed at helping to make sense out of

    the build environment. It includes the ability to view the effective

    POM and settings files, after inheritance and active profiles

    have been applied, as well as a describe a particular plugin goal to give usage

    information.

 

Mojos:

 

===============================================

Goal: 'active-profiles'

===============================================

Description:

 

Lists the profiles which are currently active for this build.

 

Implementation: org.apache.maven.plugins.help.ActiveProfilesMojo

Language: java

 

Parameters:

-----------------------------------------------

 

[0] Name: output

Type: java.io.File

Required: false

Directly editable: true

Description:

 

This is an optional parameter for a file destination for the output of this mojo...the

listing of active profiles per project.

 

-----------------------------------------------

 

[1] Name: projects

Type: java.util.List

Required: true

Directly editable: false

Description:

 

This is the list of projects currently slated to be built by Maven.

 

-----------------------------------------------

 

This mojo doesn't have any component requirements.

===============================================

 

... removed the other goals ...

該選項能讓你查看插件所有的目標及相關參數。但是有時候這些信息顯得太多了。這時候你可以獲取單個目標的信息,設置 mojo 參數和 plugin 參數。下面的命令列出了 Compiler 插件的 compile 目標的所有信息

mvn help:describe -Dplugin=compiler -Dmojo=compile -Dfull

Note

什麼? Mojo ?在Maven裏面,一個插件目標也被認爲是一個 “Mojo” 。

                   2.8. 關於Apache軟件許可證

Apache Maven 是基於 Apache 許可證2.0版 發佈的。如果你想閱讀該許可證,你可以查閱 ${M2_HOME}/LICENSE.txt 或者從開源發起組織的網站上查閱 http://www.opensource.org/licenses/apache2.0.php

很有可能你正在閱讀本書但你又不是律師。如果你想知道 Apache許可證2.0版意味着什麼,Apache軟件基金會收集了一個很有幫助的,關於許可證的常見問題解答(FAQ):http://www.apache.org/foundation/licence-FAQ.html。這裏是對問題“我不是律師,所有這些是什麼意思?”的回答。

它允許你:

l     • 自由的下載和使用 Apache 軟件,無論是軟件的整體還是部分, 也無論是出於個人目的,公司內部目的,還是商業目的。

l     • 在你創建的類庫或分發版本里使用 Apache 軟件。

它禁止你:

l     在沒有正當的權限下重新分發任何源於 Apache 的軟件或軟件片段。

l     以任何可能聲明或暗示基金會認可你的分發版本的形式下使用 Apache 軟件基金會擁有的標誌。

l     以任何可能聲明或暗示你創建了 Apache 軟件的形式下使用 Apache 軟件基金會擁有的標誌。

它要求你:

l     在你重新分發的包含 Apache 軟件的軟件裏,包含一份該許可證的副本。

l     對於任何包含 Apache 軟件的分發版本,提供給 Apache軟件基金會清楚的權限。

它不要求你:

l     在任何你再次發佈的包含Apache軟件的版本里,包含Apache軟件本身源代碼,或者你做的改動的源碼。

l     提交你對軟件的改動至 Apache 軟件基金會(雖然我們鼓勵這種反饋)。

               Part I. Maven實戰

第一本關於Maven的書是來自O'ReillyMaven開發者筆記,這本書通過一系列步驟介紹Maven。開發者筆記系列書籍背後的想法是,當開發人員和另一個開發人員坐在一起,經歷他曾經用來學習和編碼的整個思考過程,這樣會學得最好。雖然開發者筆記系列成功了,但筆記格式有一個缺點:筆記被設計成“集中關注於目標”,它讓你通過一系列步驟來完成某個特定的目標。而有大的參考書,或者“動物”書,提供全面的材料,覆蓋了整個的課題。兩種書都有優缺點,因此只出版一種書是有問題的。

爲了闡明這個問題,考慮如下情形,數萬的讀者讀完開發者筆記後,他們都知道了如何創建一個簡單的項目,比方說一個Maven項目從一組源文件創建一個WAR。 但是,當他們想知道更多的細節或者類似於Assembly插件的詳細描述的時候,他們會陷入僵局。因爲沒有關於Maven的寫得很好的參考手冊,他們需要在Maven站點上搜尋插件文檔,或者在一系列郵件列表中不停挑選。當人們真正開始鑽研Maven的時候,他們開始在Maven站點上閱讀無數的由很多不同的開發人員編寫的粗糙的HTML文檔,這些開發人員對爲插件編寫文檔有着完全不同的想法:數百的的開發人員有着不同的書寫風格,語氣以其方言。除去很多好心的志願者所做的努力,在Maven站點上閱讀插件文檔,最好的情況下,令人有受挫感,最壞的情況下,成爲了拋棄Maven的理由。很多情況下Maven用戶感覺“被騙了”因爲他們不能從站點文檔上找到答案。

雖然第一本Maven開發者筆記吸引了很多Maven 的新用戶,爲很多人培訓了最基本的Maven用例,但同樣多的讀者,當他們不能找到準確的寫得很好的參考材料的時候,感覺到了挫敗感。很多年來,缺少可靠的(或者權威的)參考資料阻礙了Maven的發展;這也成了一種Maven用戶社區成長的阻礙力量。本書想要改變這種情況,通過提供,第一:在Part I, “Maven實戰”中更新原本的Maven開發者筆記,第二:在Part II, “Maven Reference”中第一次嘗試提供全面的參考。在你手裏的(或者屏幕上的)實際上是二書合一。

本書的這個部分中,我們不會拋棄開發者筆記中的描述性推進方式,這是幫助人們“以實戰方式”學習Maven的很有價值的材料。在本書的第一部分中我們“邊做邊介紹”,然後在Part II, “Maven Reference”中我們填充空白,鑽研細節,介紹那些Maven新手不感興趣的高級話題。Part II, “Maven Reference”可能使用與樣例項目無關的一個參考列表和一程序列表,而Part I, “Maven實戰”將由實際樣例和故事驅動。讀完Part I, “Maven實戰”後,你將會擁有幾個月內開始使用Maven所需要的一切。只有當你需要通過編寫定製插件來開始自定義你的項目,或者想了解特定插件細節的時候,纔可能需要回到Part II, “Maven Reference”

Table of Contents

3. 一個簡單的Maven項目

3.1. 簡介

3.1.1. 下載本章的例子

3.2. 創建一個簡單的項目

3.3. 構建一個簡單的項目

3.4. 簡單的項目對象模型 (Project Object Model)

3.5. 核心概念

3.5.1. Maven插件和目標 (Plugins and Goals)

3.5.2. Maven生命週期 (Lifecycle)

3.5.3. Maven座標 (Coordinates)

3.5.4. Maven倉庫(Repositories)

3.5.5. Maven依賴管理 (Dependency Management)

3.5.6. 站點生成和報告 (Site Generation and Reporting)

3.6. 小結

4. 定製一個Maven項目

4.1. 介紹

4.1.1. 下載本章樣例

4.2. 定義Simple Weather項目

4.2.1. Yahoo! Weather RSS

4.3. 創建Simple Weather項目

4.4. 定製項目信息

4.5. 添加新的依賴

4.6. Simple Weather源碼

4.7. 添加資源

4.8. 運行Simple Weather項目

4.8.1. Maven Exec 插件

4.8.2. 瀏覽你的項目依賴

4.9. 編寫單元測試

4.10. 添加測試範圍依賴

4.11. 添加單元測試資源

4.12. 執行單元測試

4.12.1. 忽略測試失敗

4.12.2. 跳過單元測試

4.13. 構建一個打包好的命令行應用程序

5. 一個簡單的Web應用

5.1. 介紹

5.1.1. 下載本章樣例

5.2. 定義這個簡單的Web應用

5.3. 創建這個簡單的Web應用

5.4. 配置Jetty插件

5.5. 添加一個簡單的Servlet

5.6. 添加J2EE依賴

5.7. 小結

6. 一個多模塊項目

6.1. 簡介

6.1.1. 下載本章樣例

6.2. simple-parent 項目

6.3. simple-weather 模塊

6.4. simple-webapp 模塊

6.5. 構建這個多模塊項目

6.6. 運行Web應用

7. 多模塊企業級項目

7.1. 簡介

7.1.1. 下載本章樣例

7.1.2. 多模塊企業級項目

7.1.3. 本例中所用的技術

7.2. simple-parent項目

7.3. simple-model模塊

7.4. simple-weather模塊

7.5. simple-persist模塊

7.6. simple-webapp模塊

7.7. 運行這個Web應用

7.8. simple-command模塊

7.9. 運行這個命令行程序

7.10. 小結

7.10.1. 編寫接口項目程序

8. 優化和重構POM

8.1. 簡介

8.2. POM清理

8.3. 優化依賴

8.4. 優化插件

8.5. 使用Maven Dependency插件進行優化

8.6. 最終的POM

8.7. 小結

                   Chapter 3. 一個簡單的Maven項目

3.1. 簡介

3.1.1. 下載本章的例子

3.2. 創建一個簡單的項目

3.3. 構建一個簡單的項目

3.4. 簡單的項目對象模型 (Project Object Model)

3.5. 核心概念

3.5.1. Maven插件和目標 (Plugins and Goals)

3.5.2. Maven生命週期 (Lifecycle)

3.5.3. Maven座標 (Coordinates)

3.5.4. Maven倉庫(Repositories)

3.5.5. Maven依賴管理 (Dependency Management)

3.5.6. 站點生成和報告 (Site Generation and Reporting)

3.6. 小結

                   3.1. 簡介

本章我們介紹一個用Maven Archetype插件從空白開始創建的簡單項目。當你跟着這個簡單項目的開發過程,你會看到這個簡單的應用給我們提供了介紹Maven核心概念的機會。

在 你能開始使用Maven做複雜的,多模塊的構建之前,我們需要從基礎開始。如果你之前用過Maven,你將會注意到這裏很好的照顧到了細節。你的構建傾向於“只要能工作”,只有當你需要編寫插件來自定義默認行爲的時候,才需要深入Maven的細節。另一方面,當你需要深入Maven細節的時候,對 Maven核心概念的徹底理解是至關重要的。本章致力於爲你介紹一個儘可能簡單的Maven項目,然後介紹一些使Maven成爲一個可靠的構建平臺的核心概念。讀完本章後,你將會對構建生命週期 (build lifecycle),Maven倉庫(repositories),依賴管理 (dependency management)和項目對象模型 (Project Object Model)有一個基本的理解。

                        3.1.1. 下載本章的例子

本章開發了一個十分簡單的例子,它將被用來探究Maven的核心概念。如果你跟着本章表述的步驟,你應該不需要下載這些例子來重新創建那些Maven已經生成好的代碼。我們將會使用Maven Archetype插件來創建這個簡單的項目,本章不會以任何方式修改這個項目。如果你更喜歡通過最終的例子源代碼來閱讀本章,本章的例子項目和這本書的例子代碼可以從這裏下載到:http://www.sonatype.com/book/mvn-examples-1.0.zip或者http://www.sonatype.com/book/mvn-examples-1.0.tar.gz。解壓存檔文件到任何目錄下,然後到ch03/目錄。在ch03/目錄你將看到一個名字爲simple/的目錄,它包含了本章的源代碼。如果你希望在Web瀏覽器裏看這些例子代碼,訪問http://www.sonatype.com/book/examples-1.0並且點擊ch03/目錄。

                   3.2. 創建一個簡單的項目

開始一個新的Maven項目,在命令行使用Maven Archetype插件。

$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 /

                                         -DartifactId=simple /

                                         -DpackageName=org.sonatype.mavenbook

[INFO] Scanning for projects...

[INFO] Searching repository for plugin with prefix: 'archetype'.              

[INFO] artifact org.apache.maven.plugins:maven-archetype-plugin: checking for /

       updates from central

[INFO] -----------------------------------------------------------------------

[INFO] Building Maven Default Project

[INFO]    task-segment: [archetype:create] (aggregator-style)

[INFO] --------------------------------------------------------------------

[INFO] [archetype:create]

[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: /

       checking for updates from central

[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch03

[INFO] Parameter: packageName, Value: org.sonatype.mavenbook

[INFO] Parameter: basedir, Value: /Users/tobrien/svnw/sonatype/examples

[INFO] Parameter: package, Value: org.sonatype.mavenbook

[INFO] Parameter: version, Value: 1.0-SNAPSHOT

[INFO] Parameter: artifactId, Value: simple

[INFO] * End of debug info from resources from generated POM *

[INFO] Archetype created in dir: /Users/tobrien/svnw/sonatype/examples/simple

mvn 是Maven2的命令。archetype:create稱爲一個Maven目標 (goal)。如果你熟悉Apache Ant,一個Maven目標類似於一個Ant目標 (target);它們都描述了將會在構建中完成的工作單元 (unit of work)。而像-Dname=value這樣的對是將會被傳到目標中的參數,它們使用-D屬性這樣的形式[1],類似於你通過命令行向Java虛擬機傳遞系統屬性。archetype:create這 個目標的目的通過archetype快速創建一個項目。在這裏,一個archetype被定義爲“一個原始的模型或者類型,在它之後其它類似的東西與之匹 配;一個原型(prototype)”。Maven有許多可用的archetype,從生成一個簡單的Swing應用,到一個複雜的Web應用。本章我們 用最基本的archetype來創建一個入門項目的骨架。這個插件的前綴是“archetype”,目標爲”create”。[1]

我們已經生成了一個項目,看一下Maven在simple目錄下創建的目錄結構:

simple/

simple/pom.xml

      /src/

      /src/main/

          /main/java

      /src/test/

          /test/java

這個生成的目錄遵循Maven標準目錄佈局,我們之後會去看更多的細節,但是,現在讓我們只是嘗試瞭解這些基本的目錄。

 

Maven Archtype插件創建了一個與artifactId匹配的目錄——simple。這是項目的基礎目錄。

 

每個項目在文件pom.xml裏有它的項目對象模型 (POM)。這個文件描述了這個項目,配置了插件,聲明瞭依賴。

 

我們項目的源碼了資源文件被放在了src/main目錄下面。在我們簡單Java項目這樣的情況下,這個目錄包含了一下java類和一些配置文件。在其它的項目中,它可能是web應用的文檔根目錄,或者還放一些應用服務器的配置文件。在一個Java項目中,Java類放在src/main/java下面,而classpath資源文件放在src/main/resources下面。

 

我們項目的測試用例放在src/test下。在這個目錄下面,src/test/java存放像使用JUnit或者TestNG這樣的Java測試類。目錄src/test/resources下存放測試classpath資源文件。

Maven Archtype插件生成了一個簡單的類org.sonatype.mavenbook.App,它是一個僅有13行代碼的Java,所做的只是在main方法中輸出一行消息:

package org.sonatype.mavenbook;

 

/**

 * Hello world!

 *

 */

public class App

{

    public static void main( String[] args )

    {

        System.out.println( "Hello World!" );

    }

}

最簡單的Maven archetype生成最簡單的Maven項目:一個往標準輸出打印“Hello World”的程序。

                   3.3. 構建一個簡單的項目

一旦你遵循Section 3.2, “創建一個簡單的項目”使用Maven Archetype插件創建了一個項目,你會希望構建並打包這個應用。想要構建打包這個應用,在包含pom.xml的目錄下運行mvn install

$ mvn install

[INFO] Scanning for projects...

[INFO] ----------------------------------------------------------------------------

[INFO] Building simple

[INFO]    task-segment: [install]

[INFO] ----------------------------------------------------------------------------

[INFO] [resources:resources]

[INFO] Using default encoding to copy filtered resources.

[INFO] [compiler:compile]

[INFO] Compiling 1 source file to /simple/target/classes

[INFO] [resources:testResources]

[INFO] Using default encoding to copy filtered resources.

[INFO] [compiler:testCompile]

[INFO] Compiling 1 source file to /simple/target/test-classes

[INFO] [surefire:test]

[INFO] Surefire report directory: /simple/target/surefire-reports

 

-------------------------------------------------------

 T E S T S

-------------------------------------------------------

Running org.sonatype.mavenbook.AppTest

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.105 sec

 

Results :

 

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

 

[INFO] [jar:jar]

[INFO] Building jar: /simple/target/simple-1.0-SNAPSHOT.jar

[INFO] [install:install]

[INFO] Installing /simple/target/simple-1.0-SNAPSHOT.jar to /

  ~/.m2/repository/org/sonatype/mavenbook/ch03/simple/1.0-SNAPSHOT/ /

  simple-1.0-SNAPSHOT.jar

你已經創建了,編譯了,測試了,打包了,並且安裝了(installed)最簡單的Maven項目。在命令行運行它以向你自己驗證這個程序能工作。

$ java -cp target/simple-1.0-SNAPSHOT.jar org.sonatype.mavenbook.App

Hello World!

                   3.4. 簡單的項目對象模型 (Project Object Model)

當Maven運行的時候它向項目對象模型(POM)查看關於這個項目的信息。POM回答類似這樣的問題:這個項目是什麼類型的?這個項目的名稱是什麼?這個項目的構建有自定義麼?這裏是一個由Maven Archetype插件的create目標創建的默認的pom.xml文件。

Example 3.1. Simple 項目的 pom.xml 文件

<project xmlns="http://maven.apache.org/POM/4.0.0"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.sonatype.mavenbook.ch03</groupId>

  <artifactId>simple</artifactId>

  <packaging>jar</packaging>

  <version>1.0-SNAPSHOT</version>

  <name>simple</name>

  <url>http://maven.apache.org</url>

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

 

這個pom.xml文件是你將會面對的Maven項目中最基礎的POM,一般來說一個POM文件會複雜得多:定義多個依賴,自定義插件行爲。最開始的幾個元素——groupId,artifactId, packaging, version——是Maven的座標(coordinates),它們唯一標識了一個項目。name和url是POM提供的描述性元素,它們給人提供了可閱讀的名字,將一個項目關聯到了項目web站點。最後,dependencies元素定義了一個單獨的,測試範圍(test-scoped)依賴,依賴於稱爲JUnit的單元測試框架。這些話題將會Section 3.5, “核心概念”被深入介紹,當前,你所需知道的是,pom.xml是一個讓Maven跑起來的文件。

當Maven運行的時候,它是根據項目的pom.xml裏設置的組合來運行的,一個最上級的POM定義了Maven的安裝目錄,在這個目錄中全局的默認值被定義了,(可能)還有一些用戶定義的設置。想要看這個“有效的 (effective)”POM,或者說Maven真正運行根據的POM,在simple項目的基礎目錄下跑下面的命令。

mvn help:effective-pom

一旦你運行了此命令,你應該能看到一個大得多的POM,它暴露了Maven的默認設置

                   3.5. 核心概念

我們已經第一次運行了Maven,是時候介紹一些Maven的核心概念了。在之前的例子中,我們生了一個項目,它包含了一個POM和 一些源代碼,它們一起組成了Maven的標準目錄佈局。之後你用生命週期階段(phase)作爲參數來運行Maven,這個階段會提示Maven運行一系 列Maven插件的目標。最後,你把Maven構件(artifact)安裝(install)到了你本地倉庫(repository)。等等?什麼是生命週期?什麼是“本地倉庫”?下面的小結闡述了一些Maven的核心概念。

                        3.5.1. Maven插件和目標 (Plugins and Goals)

在前面一節中,我們用兩種類型的命令行參數運行了Maven。第一條命令是一條單個的插件目標,Archetype插件的create目標。Maven第二次運行是一個生命週期階段 –install。爲了運行單個的Maven插件目標,我們使用mvn archetype:create這樣的語法,這裏archetype是一個插件標識而create是目標標識。當Maven運行一個插件目標,它向標準輸出打印出插件標識和目標標識:

$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 /

                                        -DartifactId=simple /

                                        -DpackageName=org.sonatype.mavenbook

...

[INFO] [archetype:create]

[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: /

       checking for updates from central

...

一個Maven插件是一個單個或者多個目標的集合。Maven插件的例子有一些簡單但核心的插件,像Jar插件,它包含了一組創建JAR文 件的目標,Compiler插件,它包含了一組編譯源代碼和測試代碼的目標,或者Surefire插件,它包含一組運行單元測試和生成測試報告的目標。而 其它的,更有專門的插件包括:Hibernate3插件,用來集成流行的持久化框架Hibernate,JRuby插件,它讓你能夠讓運行ruby稱爲 Maven構建的一部分或者用Ruby來編寫Maven插件。Maven也提供了自定義插件的能力。一個定製的插件可以用Java編寫,或者用一些其它的語言如Ant,Groovy,beanshell和之前提到的Ruby。

Figure 3.1. 一個插件包含一些目標

 

一個目標是一個明確的任務,它可以作爲單獨的目標運行,或者作爲一個大的構建的一部分和其它目標一起運行。一個目標是Maven中的一個“工作單元(unit of work)”。目標的例子包括Compiler插件中的compile目標,它用來編譯項目中的所有源文件,或者Surefire插件中的test目標,用來運行單元測試。目標通過配置屬性進行配置,以用來定製行爲。例如,Compiler插件的compile目標定義了一組配置參數,它們允許你設置目標JDK版本或者選擇是否用編譯優化。在之前的例子中,我們通過命令行參數-DgroupId=org.sonatype.mavenbook.ch03-DartifactId=simple向Archetype插件的create目標傳入了groupId和artifactId配置參數。我們也向create目標傳入了packageName參數,它的值爲org.sonatype.mavenbook。如果我們忽略了packageName參數,那麼包名的默認值爲org.sonatype.mavenbook.ch03。

Note

當提到一個插件目標的時候,我們常常用速記符號:pluginId:goalId。例如,當提到Archetype插件的create目標的時候,我們寫成archetype:create。

目標定義了一些參數,這些參數可以定義一些明智的默認值。在archetype:create這個例子中,我們並沒有在命令行中指定這個目標創建什麼類型的archetype,我們簡單的傳入一個groupId和一個artifactId。這是我們對於約定優於配置(convention over configuration)的第一筆。這裏create目標的約定,或者默認值,是創建一個簡單的項目,叫做Quickstart。create目標定義了一個配置屬性archetypeArtifactId,它有一個默認值爲maven-archetype-quickstart。Quickstart archetype生成了一個最小項目的軀殼,包括一個POM和一個類。Archetype插件比第一個例子中的樣子強大得多,但是這是一個快速開始新項目的不錯的方法。在本書的後面,我們將會讓你看到Archetype插件可以用來生成複雜如web應用的項目,以及你如何能夠使用Archetype插件來定義你自己項目的集合。

Maven的核心對你項目構建中特定的任務幾乎毫無所知。就它本身來說,Maven不知道如何編譯你的代碼,它甚至不知道如何製作一個JAR文 件,它把所有這些任務代理給了Maven插件,像Compiler插件和Jar插件,它們在需要的時候被下載下來並且定時的從Maven中央倉庫更新。當 你下載Maven的時候,你得到的是一個包含了基本軀殼的Maven核心,它知道如何解析命令行,管理classpath,解析POM文 件,在需要的時候下載Maven插件。通過保持Compiler插件和Maven核心分離,並且提供更新機制,用戶很容易能使用編譯器最新的版本。通過這 種方式,Maven插件提供了通用構建邏輯的全局重用性,有不會在構建週期中定義編譯任務,有使用了所有Maven用戶共享的Compiler插件。如果 有個對Compiler插件的改進,每個使用Maven的項目可以立刻從這種變化中得到好處。(並且,如果你不喜歡這個Compiler插件,你可以用你的實現來覆蓋它)。

                        3.5.2. Maven生命週期 (Lifecycle)

上一節中,我們運行的第二個命令是mvn package。 命令行並沒有指定一個插件目標,而是指定了一個Maven生命週期階段。一個階段是在被Maven稱爲“構建生命週期”中的一個步驟。生命週期是包含在一個項目構建中的一系列有序的階段。Maven可以支持許多不同的生命週期,但是最常用的生命週期是默認的Maven生命週期,這個生命週期中一開始的一個階段是驗證項目的基本完整性,最後的一個階段是把一個項目發佈成產品。生命週期的階段被特地留得含糊,單獨的定義爲驗證(validation),測試 (testing),或者發佈(deployment),而他們對不同項目來說意味着不同的事情。例如,打包(package)這個階段在一個項目裏生成一個JAR,它也就意味着“將一個項目打成一個jar”,而在另外一個項目裏,打包這個階段可能生成一個WAR文件。Figure 3.2, “一個生命週期是一些階段的序列”展示了默認Maven生命週期的簡單樣子。

Figure 3.2. 一個生命週期是一些階段的序列

 

插件目標可以附着在生命週期階段上。隨着Maven沿着生命週期的階段移動,它會執行附着在特定階段上的目標。每個階段可能綁定了零個或者多個目標。在之前的小節裏,當你運行mvn package,你可能已經注意到了不止一個目標被執行了。檢查運行mvn package之後的輸出,會注意到那些被運行的各種目標。當這個簡單例子到達package階段的時候,它運行了Jar插件的jar目標。既然我們的簡單的quickstart項目(默認)是jar包類型,jar:jar目標被就綁定到了打包階段。

Figure 3.3. 一個目標綁定到一個階段

 

我們知道,在包類型爲jar的項目中,打包階段將會創建一個JAR文件。但是,在它之前的目標做什麼呢,像compiler:compile和surefire:test?在Maven經過它生命週期中package之前的階段的時候,這些目標被運行了;Maven執行一個階段的時候,它首先會有序的執行前面的所有階段,到命令行指定的那個階段爲止。每個階段對應了零個或者多個目標。我們沒有進行任何插件配置或者定製,所以這個例子綁定了一組標準插件的目標到默認的生命週期。當Maven經過以package爲結尾的默認生命週期的時候,下面的目標按順序被執行:

resources:resources

Resources插件的resources目標綁定到了resources 階段。這個目標複製src/main/resources下的所有資源和其它任何配置的資源目錄,到輸出目錄。

compiler:compile

Compiler插件的compile目標綁定到了compile 階段。這個目標編譯src/main/java下的所有源代碼和其他任何配置的資源目錄,到輸出目錄。

resources:testResources

Resources插件的testResources目標綁定到了test-resources 階段。這個目標複製src/test/resources下的所有資源和其它任何的配置的測試資源目錄,到測試輸出目錄。

compiler:testCompile

Compiler插件的testCompile目標綁定到了test-compile 階段。這個目標編譯src/test/java下的測試用例和其它任何的配置的測試資源目錄,到測試輸出目錄。

surefire:test

Surefire插件的test目標綁定到了test 階段。這個目標運行所有的測試並且創建那些捕捉詳細測試結果的輸出文件。默認情況下,如果有測試失敗,這個目標會終止。

jar:jar

Jar插件的jar目標綁定到了package 階段。這個目標把輸出目錄打包成JAR文件。

Figure 3.4. 被綁定的目標隨着它們階段的運行而運行

 

總結得來說,當我們運行mvn package,Maven運行到打包爲止的所有階段,在Maven沿着生命週期一步步向前的過程中,它運行綁定在每個階段上的所有目標。你也可以像下面這樣顯式的指定一系列插件目標,以得到同樣的結果:

mvn resources:resources /

    compiler:compile /

    resources:testResources /

    compiler:testCompile /

    surefire:test /

    jar:jar

運行package階段能很好的跟蹤一個特定的構建中包含的所有目標,它 也允許每個項目使用Maven來遵循一組定義明確的標準。而這個生命週期能讓開發人員從一個Maven項目跳到另外一個Maven項目,而不用知道太多每個項目構建的細節。如果你能夠構建一個Maven項目,那麼你就能構建所有的Maven項目。

                        3.5.3. Maven座標 (Coordinates)

Archetype插件通過名字爲pom.xml的文件創建了一個項目。這就是項目對象模型(POM),一個項目的聲明性描述。當Maven運行一個目標的時候,每個目標都會訪問定義在項目POM裏的信息。當jar:jar目標需要創建一個JAR文件的時候,它通過觀察POM來找出這個Jar文件的名字。當compiler:compile任務編譯Java源代碼爲字節碼的時候,它通過觀察POM來看是否有編譯目標的參數。目標在POM的上下文中運行。目標是我們希望針對項目運行的動作,而項目是通過POM定義的。POM爲項目命名,提供了項目的一組唯一標識符(座標),並且通過依賴 (dependencies) ,父 (parents) 和先決條件 (prerequisite) 來定義和其它項目的關係。POM也可以自定義插件行爲,提供項目相關的社區和開發人員的信息。

Maven座標定義了一組標識,它們可以用來唯一標識一個項目,一個依賴,或者Maven POM裏的一個插件。看一下下面的POM。

Figure 3.5. 一個Maven項目的座標

 

我們加亮了這個項目的座標:groupId, artifactId, version和packaging。這些組合的標識符拼成了一個項目的座標[2]。[2]就像任何其它的座標系統,一個Maven座標是一個地址,即“空間”裏的某個點:從一般到特殊。當一個項目通過依賴,插件或者父項目引用和另外一個項目關聯的時候,Maven通過座標來精確定位一個項目。Maven座標通常用冒號來作爲分隔符來書寫,像這樣的格式:groupId:artifactId:packaging:version。在上面的pom.xml中,它的座標可以表示爲mavenbook:my-app:jar:1.0-SNAPSHOT.這個符號也適用於項目依賴,我們的項目依賴JUnit的3.8.1版本,它包含了一個對junit:junit:jar:3.8.1的依賴。

groupId

d 團體,公司,小組,組織,項目,或者其它團體。團體標識的約定是,它以創建這個項目的組織名稱的逆向域名(reverse domain name)開頭。來自Sonatype的項目有一個以com.sonatype開頭的groupId,而Apache Software的項目有以org.apache開頭的groupId。

artifactId

在groupId下的表示一個單獨項目的唯一標識符。

version

一個項目的特定版本。發佈的項目有一個固定的版本標識來指向該項目的某一個特定的版本。而正在開發中的項目可以用一個特殊的標識,這種標識給版本加上一個“SNAPSHOT”的標記。

項目的打包格式也是Maven座標的重要組成部分,但是它不是項目唯一標識符的一個部分。一個項目的groupId:artifactId:version使之成爲一個獨一無二的項目;你不能同時有一個擁有同樣的groupId,artifactId和version標識的項目。

packaging

項目的類型,默認是jar,描述了項目打包後的輸出。類型爲jar的項目產生一個JAR文件,類型爲war的項目產生一個web應用。

在 其它“Maven化”項目構成的巨大空間中,的這四個元素是定位和使用某個特定項目的關鍵因素。Maven倉庫(repositories)(公共的,私有的,和本地的)是通過這些標識符來組織的。當一個項目被安裝到本地的Maven倉庫,它立刻能被任何其它的項目所使用。而我們所需要做的只是,在其它項 目用使用Maven的唯一座標來加入對這個特定構件的依賴。

Figure 3.6. Maven空間是項目的一個座標系統

 

                        3.5.4. Maven倉庫(Repositories)

當 你第一次運行Maven的時候,你會注意到Maven從一個遠程的Maven倉庫下載了許多文件。如果這個簡單的項目是你第一次運行Maven,那麼當觸 發resources:resource目標的時候,它首先會做的事情是去下載最新版本的Resources插件。在Maven中,構件和插件是在它們被需要的時候從遠程的倉庫取來的。初始的Maven下載包的大小相當的小(1.8兆),其中一個原因是事實上這個初始Maven不包括很多插件。它只包含了 幾近赤裸的最少值,而在需要的時候再從遠程倉庫去取。Maven自帶了一個用來下載Maven核心插件和依賴的遠程倉庫地址(http://repo1.maven.org/maven2)。

你常常會寫這樣一個項目,這個項目依賴於一些既不免費也不公開的包。在這種情況下,你需要要麼在你組織的網絡裏安裝一個定製的倉庫,要麼手動的安裝這些依賴。默認的遠程倉庫可以被替換,或者增加一個你組織維護的自定義Maven倉庫的引用。有許多現成的項目允許組織管理和維護公共Maven倉庫的鏡像。

是 什麼讓Maven倉庫成爲一個Maven倉庫的呢?Maven倉庫是通過結構來定義的,一個Maven倉庫是項目構件的一個集合,這些構件存儲在一個目錄結構下面,它們的格式能很容易的被Maven所理解。在一個Maven倉庫中,所有的東西存儲在一個與Maven項目座標十分匹配的目錄結構中。你可以打 開瀏覽器,然後瀏覽中央Maven倉庫http://repo1.maven.org/maven2/ 來看這樣的結構。你會看到座標爲org.apache.commons:commons-email:1.1的構件能在目錄/org/apache/commons/commons-email/1.1/下找到,文件名爲commons-email-1.1.jar。Maven倉庫的標準是按照下面的目錄格式來存儲構件,相對於倉庫的根目錄:

/<groupId>/<artifactId>/<version>/<artifactId>-<version>.<packaging>

Maven從遠程倉庫下載構件和插件到你本機上,存儲在你的本地Maven倉庫裏。一旦Maven已經從遠程倉庫下載了一個構件,它將永遠不需要再下載一次,因爲maven會首先在本地倉庫查找插件,然後纔是其它地方。在Windows XP上,你的本地倉庫很可能在C:/Documents and Settings/USERNAME/.m2/repository,在Windows Vista上,會是C:/Users/USERNAME/.m2/repository。在Unix系統上,你的本地倉庫在~/.m2/repository。當你創建像前一節創建的簡單項目時,install階段執行一個目標,把你項目的構件安裝到你的本地倉庫。

在你的本地倉庫,你應該可以看到我們的簡單項目創建出來的構件。如果你運行mvn install命令,Maven會把我們項目的構件安裝到本地倉庫。試一下。

mvn install

...

[INFO] [install:install]

[INFO] Installing .../simple-1.0-SNAPSHOT.jar to /

       ~/.m2/repository/org/sonatype/mavenbook/simple/1.0-SNAPSHOT/ /

       simple-1.0-SNAPSHOT.jar

...

就像你能從這個命令的輸出看到的,Maven把我們項目的JAR文 件安裝到了我們的本地Maven倉庫。Maven在本地項目中通過本地倉庫來共享依賴。如果你開發了兩個項目——項目A和項目B——項目B依賴於項目A產 生的構件。當構建項目B的時候,Maven會從本地倉庫取得項目A的構件。Maven倉庫既是一個從遠程倉庫下載的構件的緩存,也允許你的項目相互依賴。

                        3.5.5. Maven依賴管理 (Dependency Management)

在本章的simple樣例中,Maven處理了JUnit依賴的座標——junit:junit:3.8.1,指向本地Maven倉庫中的/junit/junit/3.8.1/junit-3.8.1.jar。這種基於Maven座標的定位構件的能力能讓我們在項目的POM中定義依賴。如果你檢查simple項目的pom.xml文件,你會看到有一個文件中有一個段專門處理dependencies,那裏麪包含了一個單獨的依賴——JUnit

一個複雜的項目將會包含很多依賴,也有可能包含依賴於其它構件的依賴。這是Maven最強大的特徵之一,它支持了傳遞性依賴(transitive dependencies)。假如你的項目依賴於一個庫,而這個庫又依賴於五個或者十個其它的庫(就像Spring或者Hibernate那樣)。你不必找出所有這些依賴然後把它們寫在你的pom.xml裏,你只需要加上你直接依賴的那些庫,Maven會隱式的把這些庫間接依賴的庫也加入到你的項目中。Maven也會處理這些依賴中的衝突,同時能讓你自定義默認行爲,或者排除一些特定的傳遞性依賴。

讓我們看一下你運行前面的樣例的時候那些下載到你本地倉庫的依賴。看一下這個目錄:~/.m2/repository/junit/junit/3.8.1/。如果你一直跟着本章的樣例,那麼這裏會有文件junit-3.8.1.jar和junit-3.8.1.pom,還有Maven用來驗證已下載構件準確性的校驗和文件。需要注意的是Maven不只是下載JUnit的JAR文件,它同時爲這個JUnit依賴下載了一個POM文件。Maven同時下載構件和POM文件的這種行爲,對Maven支持傳遞性依賴來說非常重要。

當你把項目的構件安裝到本地倉庫時,你會發現在和JAR文件同一目錄下,Maven發佈了一個稍微修改過的pom.xml的版本。存儲POM文件在倉庫裏提供給其它項目了該項目的信息,其中最重要的就是它有哪些依賴。如果項目B依賴於項目A,那麼它也依賴於項目A的依賴。當Maven通過一組Maven座標來處理依賴構件的時候,它也會獲取POM,通依賴的POM來尋找傳遞性依賴。那些傳遞性依賴就會被添加到當前項目的依賴列表中。

在Maven中一個依賴不僅僅是一個JAR。它是一個POM文件,這個POM可能也聲明瞭對其它構件的依賴。這些依賴的依賴叫做傳遞性依賴,Maven倉庫不僅僅存貯二進制文件,也存儲了這些構建的元數據(metadata),才使傳遞性依賴成爲可能。下圖展現了一個傳遞性依賴的可能場景。

Figure 3.7. Maven處理傳遞性依賴

 

在上圖中,項目A依賴於項目B和C,項目B依賴於項目D,項目C依賴於項目E,但是項目A所需要做的只是定義對B和C的依賴。當你的項目依賴於其它的項目,而這些項目又有一些小的依賴時(向Hibernate, Apache Struts 或者 Spring Framework),傳遞性依賴使之變得相當的方便。Maven同時也提供了一種機制,能讓你排除一些你不想要的傳遞性依賴。

Maven也提供了不同的依賴範圍(dependency scope)。Simple項目的pom.xml包含了一個依賴——junit:junit:jar:3.8.1——範圍是test。當一個依賴的範圍是test的時候,說明它在Compiler插件運行compile目標的時候是不可用的。它只有在運行compiler:testCompile和surefire:test目標的時候纔會被加入到classpath中。

當爲項目創建JAR文件的時候,它的依賴不會被捆綁在生成的構件中,他們只是用來編譯。當用Maven來創建WAR或者EAR,你可以配置Maven讓它在生成的構件中捆綁依賴,你也可以配置Maven,使用provided範圍,讓它排除WAR文件中特定的依賴。provided範圍告訴Maven一個依賴在編譯的時候需要,但是它不應該被捆綁在構建的輸出中。當你開發web應用的時候provided範圍變得十分有用,你需要通過Servlet API來編譯你的代碼,但是你不希望Servlet API的JAR文件包含在你web應用的WEB-INF/lib目錄中。

                        3.5.6. 站點生成和報告 (Site Generation and Reporting)

另外一個Maven的重要特徵是,它能生成文檔和報告。在simple項目的目錄下,運行以下命令:

mvn site

這將會運行site生命週期階段。它不像默認生命週期那樣,管理代碼生成,操作資源,編譯,打包等等。Site生命週期只關心處理在src/site目錄下的site內容,還有生成報告。在這個命令運行過之後,你將會在target/site目錄下看到一個項目web站點。載入target/site/index.html你會看到項目站點的基本外貌。它包含了一些報告,它們在左手邊的導航目錄的“項目報告”下面。它也包含了項目相關的信息,依賴和相關開發人員信息,在“項目信息”下面。Simple項目的web站點大部分是空的,因爲POM只包含了比較少的信息,只有項目座標,名稱,URL和一個test依賴。

在這個站點上,你會注意到一些默認的報告已經可以訪問了,有一個報告詳細描述了測試的結果。這個單元測試報告描述了項目中所有單元測試的成功和失敗信息。另外一個報告生成了項目API的JavaDoc。Maven提供了很完整的可配置的報告,像Clover報告檢查單元測試覆蓋率,JXR報告生成HTML源代碼相互間引用,這在代碼審查的時候非常有用,PMD報告針對各種編碼問題來分析源代碼,JDepend報告分析源代碼中各個包之間的依賴。通過在pom.xml中配置那些報告被包含在構建中,站點報告就可以被定製了。

                   3.6. 小結

我 們創建了一個simple項目,將其打包爲一個Jar,安裝到了Maven倉庫使之能被其它項目使用,最後生成了帶有文檔的站點。不寫一行代碼,不碰一個配置文件,我們就做到了這些。我們花了一些時間來研究Maven核心概念的定義。在下一章,我們將會自定義並修改我們項目的pom.xml文件,加入一些依賴,配置一些單元測試。

 

 

[1] "-D<name>=<value>"這種格式不是Maven定義的,它其實是Java用來設置系統屬性的方式,可以通過“java -help”查看Java的解釋。Maven的bin目錄下的腳本文件僅僅是把屬性傳入Java而已。

[2] 還有第五個,名爲classifier的很少使用的座標,將在本書後面介紹。現在你儘管可以忽略classifiers。

                   Chapter 4. 定製一個Maven項目

4.1. 介紹

4.1.1. 下載本章樣例

4.2. 定義Simple Weather項目

4.2.1. Yahoo! Weather RSS

4.3. 創建Simple Weather項目

4.4. 定製項目信息

4.5. 添加新的依賴

4.6. Simple Weather源碼

4.7. 添加資源

4.8. 運行Simple Weather項目

4.8.1. Maven Exec 插件

4.8.2. 瀏覽你的項目依賴

4.9. 編寫單元測試

4.10. 添加測試範圍依賴

4.11. 添加單元測試資源

4.12. 執行單元測試

4.12.1. 忽略測試失敗

4.12.2. 跳過單元測試

4.13. 構建一個打包好的命令行應用程序

                   4.1. 介紹

本章在上一章所介紹信息的基礎上進行開發。 你將創建一個由 Maven Archetype 插件生成的項目,添加一些依賴和一些源代碼,並且根據你的需要定製項目。本章最後,你將知道如何使用 Maven 開始創建真正的項目。

                        4.1.1. 下載本章樣例

本章我們將開發一個和 Yahoo! Weather web 服務交互的實用程序。雖然沒有樣例源碼你也應該能夠理解這個開發過程,但還是推薦你下載本章樣例源碼以作爲參考。本章的樣例項目包含在本書的樣例代碼中,你可以從兩個地方下載,http://www.sonatype.com/book/mvn-examples-1.0.zip 或者 http://www.sonatype.com/book/mvn-examples-1.0.tar.gz 。解壓存檔文件至任意目錄,然後到 ch04/ 目錄。 在 ch04/ 目錄你會看到一個名爲 simple-weather 的目錄,它包含了本章開發出來的 Maven 項目。如果你想要在瀏覽器裏看樣例代碼,訪問 http://www.sonatype.com/book/examples-1.0 ,然後點擊 ch04/ 目錄。

                   4.2. 定義Simple Weather項目

在定製本項目之前,讓我們退後一步,討論下這個 simple weather 項目。這個 simple weather 項目是什麼? 它是一個被設計成用來示範一些 Maven 特徵的樣例。 它能代表一類你可能需要構建的應用程序。 這個 simple weather 是一個基本的命令行驅動的應用程序,它接受郵政編碼輸入,然後從 Yahoo! Weather RSS 源獲取數據,然後解析數據並把結果打印到標準輸出。我們選擇該項目是有許多因素的。 首先,它很直觀;用戶通過命令行提供輸入,程序讀取郵政編碼,對 Yahoo! Weather 提交請求,之後解析結果,格式化之後輸入到屏幕。這個樣例是個簡單的 main() 函數加上一些相關支持的類;沒有企業級框架需要介紹或解釋,只有 XML 解析和一些日誌語句。其次,它提供很好的機會來介紹一些有趣的類庫,如 Velocity, Dom4j 和 Log4j。 雖然本書集中於 Maven ,但我們不會迴避那些介紹有趣工具的機會。最後,這是一個能在一章內介紹,開發及部署的樣例。

                        4.2.1. Yahoo! Weather RSS

在開始構建這個應用之前,你需要了解一下 Yahoo! Weather RSS 源。該服務是基於以下條款提供的:

“該數據源免費提供給個人和非營利性組織,作爲個人或其它非商業用途。 我們要求你提供給 Yahoo! Weather 連接你數據源應用的權限。”

換句話說,如果你考慮集成該數據源到你的商業 web 站點上,請再仔細考慮考慮,該數據源可作爲個人或其它非商業性用途。本章我們提倡的使用是個人教育用途。 要了解更多的 Yahoo! Weather 服務條款,請參考 Yahoo! Weather API 文檔:http://developer.yahoo.com/weather/ 。

                   4.3. 創建Simple Weather項目

首先,讓我們用 Maven Archetype 插件創建這個 simple weather 項目的基本輪廓。運行下面的命令,創建新項目:

$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch04 /

                                         -DartifactId=simple-weather /

                                         -DpackageName=org.sonatype.mavenbook /

                                         -Dversion=1.0

[INFO] [archetype:create]

[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: /

       checking for updates from central

[INFO] ------------------------------------------------------------------

[INFO] Using following parameters for creating Archetype: /

       maven-archetype-quickstart:RELEASE

[INFO] ------------------------------------------------------------------

[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch04

[INFO] Parameter: packageName, Value: org.sonatype.mavenbook

[INFO] Parameter: basedir, Value: ~/examples

[INFO] Parameter: package, Value: org.sonatype.mavenbook

[INFO] Parameter: version, Value: 1.0

[INFO] Parameter: artifactId, Value: simple-weather

[INFO] *** End of debug info from resources from generated POM ***

[INFO] Archetype created in dir: ~/examples/simple-weather

在 Maven Archetype 插件創建好了這個項目之後,進入到 simple-weather 目錄,看一下 pom.xml。你會看到如下的 XML 文檔:

Example 4.1. simple-wheather 項目的初始 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.sonatype.mavenbook.ch04</groupId>

  <artifactId>simple-weather</artifactId>

  <packaging>jar</packaging>

  <version>1.0</version>

  <name>simple-weather2</name>

  <url>http://maven.apache.org</url>

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

 

請注意我們給 archetype:create 目標傳入了 version 參數。它覆寫了默認值 1.0-SNAPSHOT 。本項目中,正如你從 pom.xml的 version 元素看到的,我們正在開發 simple-weather 項目的 1.0 版本。

                   4.4. 定製項目信息

在開始編寫代碼之前,讓我們先定製一些項目的信息。我們想要做的是添加一些關於項目許可證,組織以及項目相關開發人員的一些信息。這些都是你期望能在大部分項目中看到的標準信息。下面的文檔展示了提供組織信息,許可證信息和開發人員信息的 XML。

Example 4.2. 爲 pom.xml 添加組織,法律和開發人員信息

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

...

 

  <name>simple-weather</name>

  <url>http://www.sonatype.com</url>

 

  <licenses>

    <license>

      <name>Apache 2</name>

      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>

      <distribution>repo</distribution>

      <comments>A business-friendly OSS license</comments>

    </license>

  </licenses>

 

  <organization>

    <name>Sonatype</name>

    <url>http://www.sonatype.com</url>

  </organization>

 

  <developers>

    <developer>

      <id>jason</id>

      <name>Jason Van Zyl</name>

      <email>[email protected]</email>

      <url>http://www.sonatype.com</url>

      <organization>Sonatype</organization>

      <organizationUrl>http://www.sonatype.com</organizationUrl>

      <roles>

        <role>developer</role>

      </roles>

      <timezone>-6</timezone>

    </developer>

  </developers>

...

</project>

 

Example 4.2, “爲 pom.xml 添加組織,法律和開發人員信息” 中的省略號是爲了使代碼清單變得簡短。 當你在 pom.xml 中看到project 元素的開始標籤後面跟着 “…” 或者在 project 元素的結束標籤前有 “…” ,這說明我們沒有展示整個 pom.xml 文件。在上述情況中,licenses,organization 和 developers 元素是加在 dependencies 元素之前的。

                   4.5. 添加新的依賴

Simple weather 應用程序必須要完成以下三個任務:從 Yahoo! Weather 獲取 XML 數據,解析 XML 數據,打印格式化的輸出至標準輸出。爲了完成這三個任務,我們需要爲項目的 pom.xml 引入一些新的依賴。 爲了解析來自 Yahoo! 的 XML 響應,我們將會使用Dom4J 和 Jaxen ,爲了格式化這個命令行程序的輸出,我們將會使用 Velocity ,我們還需要加入對 Log4j 的依賴,用來做日誌。加入這些依賴之後,我們的 dependencies 元素就成了以下模樣:

Example 4.3. 添加 Dom4J, Jaxen, Velocity 和 Log4J 作爲依賴

<project>

  [...]

  <dependencies>

    <dependency>

      <groupId>log4j</groupId>

      <artifactId>log4j</artifactId>

      <version>1.2.14</version>

    </dependency>

    <dependency>

      <groupId>dom4j</groupId>

      <artifactId>dom4j</artifactId>

      <version>1.6.1</version>

    </dependency>

    <dependency>

      <groupId>jaxen</groupId>

      <artifactId>jaxen</artifactId>

      <version>1.1.1</version>

    </dependency>

    <dependency>

      <groupId>velocity</groupId>

      <artifactId>velocity</artifactId>

      <version>1.5</version>

    </dependency>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

  [...]

</project>


正如你從上面所看到的,我們在範圍爲 test 的 JUnit 依賴基礎上又加了四個依賴元素。如果你把這些依賴添加到項目的pom.xml 文件然後運行 mvn install ,你會看到 Maven 下載這些依賴及其它傳遞性依賴到你的本地 Maven 倉庫。 我們如何找這些依賴呢?我們都“知道”適當的 groupId 和 artifactId 的值嗎?有些依賴 (像 Log4J) 被廣泛使用,以至於每次你需要使用它們的時候你都會記得它們的 groupId 和 artifactId。 而 Velocity, Dom4J 和 Jaxen 是通過一個十分有用的站點http://www.mvnrepository.com 來定位的。 該站點提供了針對 Maven 倉庫的搜索接口,你可以用它來搜索依賴。 你可以自己測試一下,載入 http://www.mvnrepository.com 然後搜索一些常用的類庫,如 Hibernate 或者 Spring Framework 。當你在這上面搜索構件時,它會顯示一個 artifactId 和所有 Maven 中央倉庫所知道的版本。 點擊某個特定的版本後,它會載入一個頁面,這個頁面就包括了你需要複製到你自己項目 pom.xml 中的依賴元素。 你經常會發現某個特定的類庫擁有多於一個的groupId,這個時候你需要通過 mvnrepository.com 來幫助確定你到底需要怎樣配置你的依賴。

                   4.6. Simple Weather源碼

Simple Weather 命令行應用程序包含五個 Java 類。

org.sonatype.mavenbook.weather.Main

這個類包含了一個靜態的 main() 函數,即系統的入口。

org.sonatype.mavenbook.weather.Weather

Weather 類是個很簡單的 Java Bean,它保存了天氣報告的地點和其它一些關鍵元素,如氣溫和溼度。

org.sonatype.mavenbook.weather.YahooRetriever

YahooRetriever 連接到 Yahoo! Weather 並且返回來自數據源數據的 InputStream 。

org.sonatype.mavenbook.weather.YahooParser

YahooParser 解析來自 Yahoo! Weather 的 XML,返回 Weather 對象。

org.sonatype.mavenbook.weather.WeatherFormatter

WeatherFormatter 接受 Weather 對象,創建 VelocityContext ,根據 Velocity 模板生成結果。

這裏我們不是想要詳細闡述樣例中的代碼,但解釋一下程序中使之運行的核心代碼還是必要的。我們假設大部分讀者已經下載的本書的源碼,但也不會忘記那些按着書一步一步往下看的讀者。 本小節列出了 simple-weather 項目的類,這些類都放在同一個包下面,org.sonatype.mavenbook.weather 。

讓我們刪掉由 archetype:create 生成 App 類和 AppTest 類,然後加入我們新的包。在 Maven 項目中,所有項目的源代碼都存儲在 src/main/java 目錄。在新項目的基礎目錄下,運行下面的命令:

cd src/test/java/org/sonatype/mavenbook

rm AppTest.java

cd ../../../../../..

cd src/main/java/org/sonatype/mavenbook

rm App.java

mkdir weather

cd weather

你已經創建了一個新的包 org.sonatype.mavenbook.weather 。 現在,我們需要把那些類放到這個目錄下面。 用你最喜歡的編輯器,創建一個新文件,名字爲 Weather.java,內容如下:

Example 4.4. Simple Weather 的 Weather 模型對象

package org.sonatype.mavenbook.weather;

 

 

public class Weather {

  private String city;

  private String region;

  private String country;

  private String condition;

  private String temp;

  private String chill;

  private String humidity;

   

  public Weather() {}

 

  public String getCity() { return city; }

  public void setCity(String city) { this.city = city; }

 

  public String getRegion() { return region; }

  public void setRegion(String region) { this.region = region; }

 

  public String getCountry() { return country; }

  public void setCountry(String country) { this.country = country; }

 

  public String getCondition() { return condition; }

  public void setCondition(String condition) { this.condition = condition; }

 

  public String getTemp() { return temp; }

  public void setTemp(String temp) { this.temp = temp; }

        

  public String getChill() { return chill; }

  public void setChill(String chill) { this.chill = chill; }

 

  public String getHumidity() { return humidity; }

  public void setHumidity(String humidity) { this.humidity = humidity; }

}

 

Weather 類定義了一個簡單的 bean ,用來存儲由 Yahoo! Weather 數據源解析出來的天氣信息。天氣數據源提供了豐富的信息,從日出日落時間,到風速和風向。爲了讓這個例子保持簡單, Weather 模型對象只保存溫度,溼度和當前天氣情況的文字描述等信息。

在同一目錄下,創建 Main.java 文件。Main 這個類有一個靜態的 main() 函數——樣例程序的入口。

Example 4.5. Simple Weather 的 Main 類

package org.sonatype.mavenbook.weather;

 

import java.io.InputStream;

 

import org.apache.log4j.PropertyConfigurator;

 

 

public class Main {

 

  public static void main(String[] args) throws Exception {

    // Configure Log4J

    PropertyConfigurator.configure(Main.class.getClassLoader()

                                       .getResource("log4j.properties"));

 

    // Read the Zip Code from the Command-line (if none supplied, use 60202)

    int zipcode = 60202;

    try {

      zipcode = Integer.parseInt(args[0]);

    } catch( Exception e ) {}

 

    // Start the program

    new Main(zipcode).start();

  }

 

  private int zip;

 

  public Main(int zip) {

    this.zip = zip;

  }

 

  public void start() throws Exception {

    // Retrieve Data

    InputStream dataIn = new YahooRetriever().retrieve( zip );

 

    // Parse Data

    Weather weather = new YahooParser().parse( dataIn );

 

    // Format (Print) Data

    System.out.print( new WeatherFormatter().format( weather ) );

  }

}

 

上例中的 main() 函數通過獲取 classpath 中的資源文件來配置 Log4J ,之後它試圖從命令行讀取郵政編碼。 如果在讀取郵政編碼的時候拋出了異常,程序會設置默認郵政編碼爲 60202 。一旦有了郵政編碼,它初始化一個 Main 對象,調用該對象的 start() 方法。而 start() 方法會調用 YahooRetriever 來獲取天氣的 XML 數據。 YahooRetriever 返回一個 InputStreem ,傳給YahooParser 。 YahooParser 解析 XML 數據並返回 Weather 對象。 最後,WeatherFormatter 接受一個 Weather 對象並返回一個格式化的 String ,打印到標準輸出。

在相同目錄下創建文件 YahooRetriever.java ,內容如下:

Example 4.6. Simple Weather 的 YahooRetriever 類

package org.sonatype.mavenbook.weather;

 

import java.io.InputStream;

import java.net.URL;

import java.net.URLConnection;

 

import org.apache.log4j.Logger;

 

public class YahooRetriever {

 

  private static Logger log = Logger.getLogger(YahooRetriever.class);

 

  public InputStream retrieve(int zipcode) throws Exception {

    log.info( "Retrieving Weather Data" );

    String url = "http://weather.yahooapis.com/forecastrss?p=" + zipcode;

    URLConnection conn = new URL(url).openConnection();

    return conn.getInputStream();

  }

}

 

這個簡單的類打開一個連接到 Yahoo! Weather API 的 URLConnection 並返回一個 InputStream 。 我們還需要在該目錄下創建文件 YahooParser.java 用以解析這個數據源。

Example 4.7. Simple Weather 的 YahooParser 類

package org.sonatype.mavenbook.weather;

 

import java.io.InputStream;

import java.util.HashMap;

import java.util.Map;

 

import org.apache.log4j.Logger;

import org.dom4j.Document;

import org.dom4j.DocumentFactory;

import org.dom4j.io.SAXReader;

 

public class YahooParser {

 

  private static Logger log = Logger.getLogger(YahooParser.class);

 

  public Weather parse(InputStream inputStream) throws Exception {

    Weather weather = new Weather();

 

    log.info( "Creating XML Reader" );

    SAXReader xmlReader = createXmlReader();

    Document doc = xmlReader.read( inputStream );

 

    log.info( "Parsing XML Response" );

    weather.setCity( doc.valueOf("/rss/channel/y:location/@city") );

    weather.setRegion( doc.valueOf("/rss/channel/y:location/@region") );

    weather.setCountry( doc.valueOf("/rss/channel/y:location/@country") );

    weather.setCondition( doc.valueOf("/rss/channel/item/y:condition/@text") );

    weather.setTemp( doc.valueOf("/rss/channel/item/y:condition/@temp") );

    weather.setChill( doc.valueOf("/rss/channel/y:wind/@chill") );

    weather.setHumidity( doc.valueOf("/rss/channel/y:atmosphere/@humidity") );

 

    return weather;

  }

 

  private SAXReader createXmlReader() {

    Map<String,String> uris = new HashMap<String,String>();

        uris.put( "y""http://xml.weather.yahoo.com/ns/rss/1.0" );

       

    DocumentFactory factory = new DocumentFactory();

    factory.setXPathNamespaceURIs( uris );

       

    SAXReader xmlReader = new SAXReader();

    xmlReader.setDocumentFactory( factory );

    return xmlReader;

  }

}

 

YahooParser 是本例中最複雜的類,我們不會深入 Dom4J 或者 Jaxen 的細節,但是這個類還是需要一些解釋。YahooParser 的parse() 方法接受一個 InputStrem 然後返回一個 Weather 對象。 爲了完成這一目標,它需要用 Dom4J 來解析 XML 文檔。因爲我們對 Yahoo! Weather XML 命名空間的元素感興趣,我們需要用 createXmlReader() 方法創建一個包含命名空間信息的SAXReader 。 一旦我們創建了這個 reader 並且解析了文檔,得到了返回的 org.dom4j.Document ,只需要簡單的使用 XPath 表達式來獲取需要的信息,而不是遍歷所有的子元素。本例中 Dom4J 提供了 XML 解析功能,而 Jaxen 提供了 XPath 功能。

我們已經創建了 Weather 對象,我們需要格式化輸出以供人閱讀。在同一目錄中創建一個名爲 WeatherFormatter.java 的文件。

Example 4.8. Simple Weather 的 WeatherFormatter 類

package org.sonatype.mavenbook.weather;

 

import java.io.InputStreamReader;

import java.io.Reader;

import java.io.StringWriter;

 

import org.apache.log4j.Logger;

import org.apache.velocity.VelocityContext;

import org.apache.velocity.app.Velocity;

 

public class WeatherFormatter {

 

  private static Logger log = Logger.getLogger(WeatherFormatter.class);

 

  public String format( Weather weather ) throws Exception {

    log.info( "Formatting Weather Data" );

    Reader reader =

      new InputStreamReader( getClass().getClassLoader()

                                 .getResourceAsStream("output.vm"));

    VelocityContext context = new VelocityContext();

    context.put("weather", weather );

    StringWriter writer = new StringWriter();

    Velocity.evaluate(context, writer, "", reader);

    return writer.toString();

  }

}

 

WeatherFormatter 使用 Veloticy 來呈現一個模板。format() 方法接受一個 Weather bean 然後返回格式化好的 String。format() 方法做的第一件事是從 classpath 載入名字爲 output.vm 的 Velocity 模板。然後我們創建一個 VelocityContext ,它需要一個 Weather 對象來填充。 一個StringWriter被創建用來存放模板生成的結果數據。通過調用 Velocity.evaluate(),給模板賦值,結果作爲 String 返回。

在我們能夠運行該樣例程序之前,我們需要往 classpath 添加一些資源。

                   4.7. 添加資源

本項目依賴於兩個 classpath 資源: Main 類通過 classpath 資源 log4j.preoperties 來配置 Log4J , WeatherFormatter 引用了一個在 classpath 中的名爲 output.vm 的 Velocity 模板。這兩個資源都需要在默認包中(或者 classpath 的根目錄)。

爲了添加這些資源,我們需要在項目的基礎目錄下創建一個新的目錄—— src/main/resources。 由於任務 archetype:create沒有創建這個目錄,我們需要通過在項目的基礎目錄下運行下面的命令來創建它:

cd src/main

mkdir resources

cd resources

在這個資源目錄創建好之後,我們可以加入這兩個資源。首先,往目錄 resources 加入文件 log4j.properties。

Example 4.9. Simple Weather 的 Log4J 配置文件

# Set root category priority to INFO and its only appender to CONSOLE.

log4j.rootCategory=INFO, CONSOLE

 

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.

log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender

log4j.appender.CONSOLE.Threshold=INFO

log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout

log4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p %c{1} %x - %m%n

 

這個 log4j.properties 文件簡單配置了 Log4J ,使其使用 PatternLayout 往標準輸出打印所有日誌信息。最後,我們需要創建output.vm ,它是這個命令行程序用來呈現輸出的 Velocity 模板。 在 resources 目錄創建 output.vm 。

Example 4.10. Simple Weather 的 Output Velocity 模板

*********************************

 Current Weather Conditions for:

  ${weather.city}, ${weather.region}, ${weather.country}

 

 Temperature: ${weather.temp}

   Condition: ${weather.condition}

    Humidity: ${weather.humidity}

  Wind Chill: ${weather.chill}

*********************************

這個模板包含了許多對名爲 weather 的變量的引用。 這個 weather 變量是傳給 WeatherFormatter 的 那個 Weather bean,${weather.temp} 語法簡化的表示獲取並顯示 temp 這個bean屬性的值。現在我們已經在正確的地方有了我們項目的所有代碼,我們可以使用 Maven 來運行這個樣例。

 

                   4.8. 運行Simple Weather項目

使用來自 Codehaus Mojo 項目 的 Exec 插件,我們可以運行這個程序。在項目的基礎目錄下運行以下命令,以運行該程序的 Main類。

mvn install

mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main

...

[INFO] [exec:java]

0    INFO  YahooRetriever  - Retrieving Weather Data

134  INFO  YahooParser  - Creating XML Reader

333  INFO  YahooParser  - Parsing XML Response

420  INFO  WeatherFormatter  - Formatting Weather Data

*********************************

 Current Weather Conditions for:

  Evanston, IL, US

 

 Temperature: 45

   Condition: Cloudy

    Humidity: 76

  Wind Chill: 38

*********************************

...

我們沒有爲 Main 類提供命令行參數,因此程序按照默認的郵編執行——60202。正如你能看到的,我們已經成功的運行了 SImple Weather 命令行工具,從 Yahoo! Weather 獲取了一些數據,解析了結果,並且通過 Velocity 格式化了結果數據。我們僅僅寫了項目的源代碼,往 pom.xml 添加了一些最少的配置。 注意我們這裏沒有引入“構建過程”。 我們不需要定義如何或哪裏讓 Java 編譯器編譯我們的源代碼,我們不需要指導構建系統在運行樣例程序的時候如何定位二進制文件,我們所需要做的是包含一些依賴,用來定位合適的 Maven 座標。

                        4.8.1. Maven Exec 插件

Exec 插件允許你運行 Java 類和其它腳本。 它不是 Maven 核心插件,但它可以從 Codehaus 的 Mojo 項目得到。想要查看 Exec 插件的完整描述,運行:

mvn help:describe -Dplugin=exec -Dfull

這會列出所有 Maven Exec 插件可用的目標。 Help 插件同時也會列出 Exec 插件的有效參數,如果你想要定製 Exec 插件的行爲,傳入命令行參數,你應該使用 help:describe 提供的文檔作爲指南。 雖然 Exec 插件很有用,在開發過程中用來運行測試之外,你不應該依賴它來運行你的應用程序。想要更健壯的解決方案,使用 Maven Assembly 插件,它在Section 4.13, “構建一個打包好的命令行應用程序”中被描述。

                        4.8.2. 瀏覽你的項目依賴

Exec 插件讓我們能夠在不往 classpath 載入適當的依賴的情況下,運行這個程序。 在任何其它的構建系統能夠中,我們必須複製所有程序依賴到類似於 lib/ 的目錄,這個目錄包含一個 JAR 文件的集合。那樣,我們就必須寫一個簡單的腳本,在 classpath 中包含我們程序的二進制代碼和我們的依賴。 只有那樣我們才能運行 java org.sonatype.mavenbook.weather.Main 。 Exec 能做這樣的工作是因爲 Maven 已經知道如何創建和管理你的 classpath 和你的依賴。

瞭解你項目的 classpath 包含了哪些依賴是很方便也很有用的。這個項目不僅包含了一些類庫如 Dom4J, Log4J, Jaxen,和Velocity,它同時也引入了一些傳遞性依賴。 如果你需要找出 classpath 中有什麼,你可以使用 Maven Dependency 插件來打印出已解決依賴的列表。要打印出 Simple Weather 項目的這個列表,運行 dependency:resolve 目標。

mvn dependency:resolve

...

[INFO] [dependency:resolve]

[INFO]

[INFO] The following files have been resolved:

[INFO]    com.ibm.icu:icu4j:jar:2.6.1 (scope = compile)

[INFO]    commons-collections:commons-collections:jar:3.1 (scope = compile)

[INFO]    commons-lang:commons-lang:jar:2.1 (scope = compile)

[INFO]    dom4j:dom4j:jar:1.6.1 (scope = compile)

[INFO]    jaxen:jaxen:jar:1.1.1 (scope = compile)

[INFO]    jdom:jdom:jar:1.0 (scope = compile)

[INFO]    junit:junit:jar:3.8.1 (scope = test)

[INFO]    log4j:log4j:jar:1.2.14 (scope = compile)

[INFO]    oro:oro:jar:2.0.8 (scope = compile)

[INFO]    velocity:velocity:jar:1.5 (scope = compile)

[INFO]    xalan:xalan:jar:2.6.0 (scope = compile)

[INFO]    xerces:xercesImpl:jar:2.6.2 (scope = compile)

[INFO]    xerces:xmlParserAPIs:jar:2.6.2 (scope = compile)

[INFO]    xml-apis:xml-apis:jar:1.0.b2 (scope = compile)

[INFO]    xom:xom:jar:1.0 (scope = compile)

正如你能看到的,我們項目擁有一個很大的依賴集合。雖然我們只是爲四個類庫引入了直接的依賴,看來我們實際共引入了15個依賴。 Dom4J 依賴於 Xerces 和 XML 解析器 API ,Jaxen 依賴於 Xalan,後者也就在 classpath 中可用了。 Dependency 插件將會打印出最終的你項目編譯所基於的所有依賴的組合。如果你想知道你項目的整個依賴樹,你可以運行 dependency:tree 目標。

mvn dependency:tree

...

[INFO] [dependency:tree]

[INFO] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0

[INFO] +- log4j:log4j:jar:1.2.14:compile

[INFO] +- dom4j:dom4j:jar:1.6.1:compile

[INFO] |  /- xml-apis:xml-apis:jar:1.0.b2:compile

[INFO] +- jaxen:jaxen:jar:1.1.1:compile

[INFO] |  +- jdom:jdom:jar:1.0:compile

[INFO] |  +- xerces:xercesImpl:jar:2.6.2:compile

[INFO] |  /- xom:xom:jar:1.0:compile

[INFO] |     +- xerces:xmlParserAPIs:jar:2.6.2:compile

[INFO] |     +- xalan:xalan:jar:2.6.0:compile

[INFO] |     /- com.ibm.icu:icu4j:jar:2.6.1:compile

[INFO] +- velocity:velocity:jar:1.5:compile

[INFO] |  +- commons-collections:commons-collections:jar:3.1:compile

[INFO] |  +- commons-lang:commons-lang:jar:2.1:compile

[INFO] |  /- oro:oro:jar:2.0.8:compile

[INFO] +- org.apache.commons:commons-io:jar:1.3.2:test

[INFO] /- junit:junit:jar:3.8.1:test

...

如果你還不滿足,或者想要查看完整的依賴蹤跡,包含那些因爲衝突或者其它原因而被拒絕引入的構件,打開 Maven 的調試標記運行:

mvn install -X

...

[DEBUG] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0 (selected for null)

[DEBUG]   log4j:log4j:jar:1.2.14:compile (selected for compile)

[DEBUG]   dom4j:dom4j:jar:1.6.1:compile (selected for compile)

[DEBUG]     xml-apis:xml-apis:jar:1.0.b2:compile (selected for compile)

[DEBUG]   jaxen:jaxen:jar:1.1.1:compile (selected for compile)

[DEBUG]     jaxen:jaxen:jar:1.1-beta-6:compile (removed - causes a cycle in the graph)

[DEBUG]     jaxen:jaxen:jar:1.0-FCS:compile (removed - causes a cycle in the graph)

[DEBUG]     jdom:jdom:jar:1.0:compile (selected for compile)

[DEBUG]     xml-apis:xml-apis:jar:1.3.02:compile (removed - nearer found: 1.0.b2)

[DEBUG]     xerces:xercesImpl:jar:2.6.2:compile (selected for compile)

[DEBUG]     xom:xom:jar:1.0:compile (selected for compile)

[DEBUG]       xerces:xmlParserAPIs:jar:2.6.2:compile (selected for compile)

[DEBUG]       xalan:xalan:jar:2.6.0:compile (selected for compile)

[DEBUG]       xml-apis:xml-apis:1.0.b2.

[DEBUG]       com.ibm.icu:icu4j:jar:2.6.1:compile (selected for compile)

[DEBUG]   velocity:velocity:jar:1.5:compile (selected for compile)

[DEBUG]     commons-collections:commons-collections:jar:3.1:compile (selected for compile)

[DEBUG]     commons-lang:commons-lang:jar:2.1:compile (selected for compile)

[DEBUG]     oro:oro:jar:2.0.8:compile (selected for compile)

[DEBUG]   junit:junit:jar:3.8.1:test (selected for test)

從調試輸出我們看到一些依賴管理系統工作的內部信息。 你在這裏看到的是項目的依賴樹。 Maven 正打印出你項目的所有的依賴,以及這些依賴的依賴(還有依賴的依賴的依賴)的完整的 Maven 座標。 你能看到 simple-weather 依賴於 jaxen , jaxen 依賴於 xom , xom 接着依賴於 icu4j 。從該輸出你能看到 Maven 正在創建一個依賴圖,排除重複,解決不同版本之間的衝突。如果你的依賴有問題,通常在 dependency:tree 所生成的列表基礎上更深入一點會有幫助;開啓調試輸出允許你看到 Maven 工作時的依賴機制。

                   4.9. 編寫單元測試

Maven 內建了對單元測試的支持,測試是 Maven 默認生命週期的一部分。讓我們給 Simple Weather 項目添加一些單元測試。 首先,在 src/test/java 下面創建包 org.sonatype.mavenbook.weather。

cd src/test/java

cd org/sonatype/mavenbook

mkdir -p weather/yahoo

cd weather/yahoo

目前,我們將會創建兩個單元測試。 第一個單元測試會測試 YahooParser ,第二個會測試 WeatherFormatter。 在 weather 包中,創建一個帶有一以下內容的文件,名稱爲 YahooParserTest.java 。

Example 4.11. Simple Weather 的 YahooParserTest 單元測試

package org.sonatype.mavenbook.weather.yahoo;

 

import java.io.InputStream;

 

import junit.framework.TestCase;

 

import org.sonatype.mavenbook.weather.Weather;

import org.sonatype.mavenbook.weather.YahooParser;

 

public class YahooParserTest extends TestCase {

 

  public YahooParserTest(String name) {

    super(name);

  }

 

  public void testParser() throws Exception {

    InputStream nyData =

      getClass().getClassLoader().getResourceAsStream("ny-weather.xml");

    Weather weather = new YahooParser().parse( nyData );

    assertEquals( "New York", weather.getCity() );

    assertEquals( "NY", weather.getRegion() );

    assertEquals( "US", weather.getCountry() );

    assertEquals( "39", weather.getTemp() );

    assertEquals( "Fair", weather.getCondition() );

    assertEquals( "39", weather.getChill() );

    assertEquals( "67", weather.getHumidity() );

  }

}

 

YahooParserTest 繼承了 JUnit 定義的 TestCase 類。 它遵循了 JUnit 測試的慣例模式:一個構造函數接受一個單獨的 String參數並調用父類的構造函數,還有一系列以“test”開頭的公有方法,做爲單元測試被調用。我們定義了一個單獨的測試方法,testParser ,通過解析一個值已知的 XML 文檔來測試 YahooParser 。 測試 XML 文檔命名爲 ny-weather.xml ,從 classpath載入。我們將在Section 4.11, “添加單元測試資源”添加測試資源。 在我們這個 Maven 項目的目錄佈局中,文件 ny-weather.xml 可以從包含測試資源的目錄—— ${basedir}/src/test/resources ——中找到,路徑爲org/sonatype/mavenbook/weather/yahoo/ny-weather.xml 。 該文件作爲一個 InputStream 被讀入,傳給 YahooParser的 parse() 方法。 parse() 方法返回一個 Weather 對象,該對象通過一系列 由 TestCase 定義的 assertEquals() 調用而被測試。

在同一目錄下創建一個名爲 WeatherFormatterTest.java 的文件。

Example 4.12. Simple Weather 的 WeatherFormatterTest 單元測試

package org.sonatype.mavenbook.weather.yahoo;

 

import java.io.InputStream;

 

import org.apache.commons.io.IOUtils;

 

import org.sonatype.mavenbook.weather.Weather;

import org.sonatype.mavenbook.weather.WeatherFormatter;

import org.sonatype.mavenbook.weather.YahooParser;

 

import junit.framework.TestCase;

 

public class WeatherFormatterTest extends TestCase {

 

  public WeatherFormatterTest(String name) {

    super(name);

  }

 

  public void testFormat() throws Exception {

    InputStream nyData =

      getClass().getClassLoader().getResourceAsStream("ny-weather.xml");

    Weather weather = new YahooParser().parse( nyData );

    String formattedResult = new WeatherFormatter().format( weather );

    InputStream expected =

      getClass().getClassLoader().getResourceAsStream("format-expected.dat");

    assertEquals( IOUtils.toString( expected ).trim(), formattedResult.trim() );

  }

}

 

該項目中的第二個單元測試測試 WeatherFormatter。 和 YahooParserTest 一樣,WeatherFormatter 同樣也繼承 JUnit 的TestCase 類。 這個單獨的測試通過單元測試的 classpath 從 ${basedir}/src/test/resources 的org/sonatype/mavenbook/weather/yahoo 目錄讀取同樣的測試資源文件。我們將會在Section 4.11, “添加單元測試資源”添加測試資源。 WeatherFormatterTest 首先調用 YahooParser 解析出 Weather 對象,然後用 WeatherFormatter 格式化這個對象。我們的期望輸出被存儲在一個名爲 format-expected.dat 的文件中,該文件存放在和 ny-weather.xml 同樣的目錄中。要比較測試輸出和期望輸出,我們將期望輸出作爲 InputStream 讀入,然後使用 Commons IO 的 IOUtils 類來把文件轉化爲String 。 然後使用 assertEquals() 比較這個 String 和測試輸出。

                   4.10. 添加測試範圍依賴

在類 WeatherFormatterTest 中我們用了一個來自於 Apache Commons IO 的工具—— IOUtils 類。 IOUtils 提供了許多很有幫助的靜態方法,能幫助讓很多工作擺脫繁瑣的 I/O 操作。在這個單元測試中我們使用了 IOUtils.toString() 來複制 classpath 中資源 format.expected.dat 中的數據至 String。 不用 Commons IO 我們也能完成這件事情,但是那需要額外的六七行代碼來處理像 InputStreamReader 和 StringWriter 這樣的對象。我們使用 Commons IO 的主要原因是,能有理由添加對 Commons IO 的測試範圍依賴。

測試範圍依賴是一個只在測試編譯和測試運行時在 classpath 中有效的依賴。如果你的項目是以 war 或者 ear 形式打包的,測試範圍依賴就不會被包含在項目的打包輸出中。要添加一個測試範圍依賴,在你項目的 dependencies 小節中添加如下 dependency 元素。

Example 4.13. 添加一個測試範圍依賴

<project>

  ...

  <dependencies>

    ...

    <dependency>

      <groupId>org.apache.commons</groupId>

      <artifactId>commons-io</artifactId>

      <version>1.3.2</version>

      <scope>test</scope>

    </dependency>

    ...

  </dependencies>

</project>

 

當你往 pom.xml 中添加了這個依賴以後,運行 mvn dependency:resolve 你會看到 commons-io 出現在在依賴列表中,範圍是 test。在我們可以運行該項目的單元測試之前,我們還需要做一件事情。 那就是創建單元測試依賴的 classpath 資源。測試範圍依賴將在9.4.1節 “依賴範圍” 中詳細解釋。

                   4.11. 添加單元測試資源

一個單元測試需要訪問針對測試的一組資源。 通常你需要在測試 classpath 中存儲一些包含期望結果的文件,以及包含模擬輸入的文件。在本項目中,我們爲 YahooParserTest 準備了一個名爲 ny-weather.xml 的測試 XML 文檔,還有一個名爲 format-expected.dat 的文件,包含了 WeatherFormatter 的期望輸出。

要添加測試資源,你需要創建目錄 src/test/resources 。 這是 Maven 尋找測試資源的默認目錄。在你的項目基礎目錄下運行下面的命令以創建該目錄。

cd src/test

mkdir resources

cd resources

當你創建好這個資源目錄之後,在資源目錄下創建一個名爲 format-expected.dat 的文件。

Example 4.14. Simple Weather 的 WeatherFormatterTest 期望輸出

*********************************

 Current Weather Conditions for:

  New York, NY, US

 

 Temperature: 39

   Condition: Fair

    Humidity: 67

  Wind Chill: 39

*********************************

 

這個文件應該看起來很熟悉了,它和你用 Maven Exec 插件運行 Simple Weather 項目得到的輸出是一樣的。你需要在資源目錄添加的第二個文件是 ny-weather.xml 。

Example 4.15. Simple Weather 的 YahooParserTest XML 輸入

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>

<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"

     xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">

 <channel>

 <title>Yahoo! Weather - New York, NY</title>

 <link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link>

 <description>Yahoo! Weather for New York, NY</description>

 <language>en-us</language>

 <lastBuildDate>Sat, 10 Nov 2007 8:51 pm EDT</lastBuildDate>

 

 <ttl>60</ttl>

 <yweather:location city="New York" region="NY" country="US" />

 <yweather:units temperature="F" distance="mi" pressure="in" speed="mph" />

 <yweather:wind chill="39" direction="0" speed="0" />

 <yweather:atmosphere humidity="67" visibility="1609" pressure="30.18"

                      rising="1" />

  <yweather:astronomy sunrise="6:36 am" sunset="4:43 pm" />

  <image>

 <title>Yahoo! Weather</title>

 

 <width>142</width>

 <height>18</height>

 <link>http://weather.yahoo.com/</link>

 <url>http://l.yimg.com/us.yimg.com/i/us/nws/th/main_142b.gif</url>

 </image>

 <item>

 <title>Conditions for New York, NY at 8:51 pm EDT</title>

 

  <geo:lat>40.67</geo:lat>

 <geo:long>-73.94</geo:long>

  <link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY//</link>

 <pubDate>Sat, 10 Nov 2007 8:51 pm EDT</pubDate>

 <yweather:condition text="Fair" code="33" temp="39"

                     date="Sat, 10 Nov 2007 8:51 pm EDT" />

 <description><![CDATA[

<img src="http://l.yimg.com/us.yimg.com/i/us/we/52/33.gif" /><br />

 <b>Current Conditions:</b><br />

 Fair, 39 F<BR /><BR />

 <b>Forecast:</b><BR />

  Sat - Partly Cloudy. High: 45 Low: 32<br />

  Sun - Sunny. High: 50 Low: 38<br />

 <br />

 ]]></description>

 <yweather:forecast day="Sat" date="10 Nov 2007" low="32" high="45"

                    text="Partly Cloudy" code="29" />

 

<yweather:forecast day="Sun" date="11 Nov 2007" low="38" high="50"

                   text="Sunny" code="32" />

  <guid isPermaLink="false">10002_2007_11_10_20_51_EDT</guid>

 </item>

</channel>

</rss>

 

該文件包含了一個給 YahooParserTest 用的 XML 文檔。有了這個文件,我們不用從 Yahoo! Weather 獲取 XML 響應就能測試YahooParser 了。

                   4.12. 執行單元測試

既然你的項目已經有單元測試了,那麼讓它們運行起來吧。你不必爲了運行單元測試做什麼特殊的事情, test 階段是 Maven 生命週期中常規的一部分。 當你運行 mvn package 或者 mvn install 的時候你也運行了測試。 如果你想要運行到 test 階段爲止的所有生命週期階段,運行 mvn test 。

mvn test

...

[INFO] [surefire:test]

[INFO] Surefire report directory: ~/examples/simple-weather/target/surefire-reports

 

-------------------------------------------------------

 T E S T S

-------------------------------------------------------

Running org.sonatype.mavenbook.weather.yahoo.WeatherFormatterTest

0    INFO  YahooParser  - Creating XML Reader

177  INFO  YahooParser  - Parsing XML Response

239  INFO  WeatherFormatter  - Formatting Weather Data

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.547 sec

Running org.sonatype.mavenbook.weather.yahoo.YahooParserTest

475  INFO  YahooParser  - Creating XML Reader

483  INFO  YahooParser  - Parsing XML Response

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec

 

Results :

 

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

從命令行運行 mvn test 使 Maven 執行到 test 階段爲止的所有生命週期階段。 Maven Surefire 插件有一個 test 目標,該目標被綁定在了 test 階段。 test 目標執行項目中所有能在 src/test/java 找到的並且文件名與 **/Test*.java, **/*Test.java 和**/*TestCase.java 匹配的所有單元測試。 在本例中,你能看到 Surefire 插件的 test 目標執行了 WeatherFormatterTest 和YahooParserTest 。 在 Maven Surefire 插件執行 JUnit 測試的時候,它同時也在 ${basedir}/target/surefire-reports 目錄下生成 XML 和常規文本報告。如果你的測試失敗了,你可以去查看這個目錄,裏面有你單元測試生成的異常堆棧信息和錯誤信息。

                        4.12.1. 忽略測試失敗

通常,你會開發一個帶有很多失敗單元測試的系統。 如果你正在實踐測試驅動開發(TDD),你可能會使用測試失敗來衡量你離項目完成有多遠。 如果你有失敗的單元測試,但你仍然希望產生構建輸出,你就必須告訴 Maven 讓它忽略測試失敗。 當 Maven 遇到一個測試失敗,它默認的行爲是停止當前的構建。如果你希望繼續構建項目,即使 Surefire 插件遇到了失敗的單元測試,你就需要設置Surefire 的 testFailureIgnore 這個配置屬性爲 true。

Example 4.16. 忽略單元測試失敗

<project>

  [...]

  <build>

    <plugins>

      <plugin>

        <groupId>org.apache.maven.plugins</groupId>

        <artifactId>maven-surefire-plugin</artifactId>

        <configuration>

          <testFailureIgnore>true</testFailureIgnore>

        </configuration>

      </plugin>

    </plugins>

  </build>

  [...]

</project>

 

該插件文檔 (http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html) 說明,這個參數聲明爲一個表達式:

Example 4.17. 插件參數表達式

       testFailureIgnore  Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite convenient on occasion.

 

    * Type: boolean

    * Required: No

    * Expression: ${maven.test.failure.ignore}

      

 

這個表達式可以從命令行通過 -D 參數設置。

mvn test -Dmaven.test.failure.ignore=true

                        4.12.2. 跳過單元測試

你可能想要配置 Maven 使其完全跳過單元測試。可能你有一個很大的系統,單元測試需要花好多分鐘來完成,而你不想在生成最終輸出前等單元測試完成。你可能正工作在一個遺留系統上面,這個系統有一系列的失敗的單元測試,你可能僅僅想要生成一個 JAR而不是去修復所有的單元測試。 Maven 提供了跳過單元測試的能力,只需要使用 Surefire 插件的 skip 參數。 在命令行,只要簡單的給任何目標添加 maven.test.skip 屬性就能跳過測試:

$ mvn install -Dmaven.test.skip=true

...

[INFO] [compiler:testCompile]

[INFO] Not compiling test sources

[INFO] [surefire:test]

[INFO] Tests are skipped.

...

當 Surefire 插件到達 test 目標的時候,如果 maven.test.skip 設置爲 true ,它就會跳過單元測試。 另一種配置 Maven 跳過單元測試的方法是給你項目的 pom.xml 添加這個配置。 你需要爲你的 build 添加 plugin 元素。

Example 4.18. 跳過單元測試

<project>

  [...]

  <build>

    <plugins>

      <plugin>

        <groupId>org.apache.maven.plugins</groupId>

        <artifactId>maven-surefire-plugin</artifactId>

        <configuration>

          <skip>true</skip>

        </configuration>

      </plugin>

    </plugins>

  </build>

  [...]

</project>

 

                   4.13. 構建一個打包好的命令行應用程序

在 Section 4.12, “執行單元測試”,我們使用 Maven Exec 插件運行了 Simple Weather 應用程序。雖然 Maven Exec 能運行程序並且產生輸出,你不能就把 Maven 當成是你程序運行的容器。如果你把這個命令行程序分發給其他人,你大概就需要分發一個 JAR 或者一個ZIP 存檔文件或者 TAR 壓縮過的 GZIP 文件。下面的小節介紹了使用 Maven Assembly 插件的預定義裝配描述符生成一個可分發的JAR 文件的過程,該文件包含了項目的二進制文件和所有的依賴。

Maven Assembly 插件是一個用來創建你應用程序特有分發包的插件。 你可以使用 Maven Assembly 插件以你希望的任何形式來裝配輸出,只需定義一個自定義的裝配描述符。後面的章節我們會說明如何創建一個自定義裝配描述符,爲 Simple Weather 應用程序生成一個更復雜的存檔文件。 本章我們將會使用預定義的 jar-with-dependencies 格式。 要配置 Maven Assembly 插件,我們需要在 pom.xml 中的 build 配置中添加如下的 plugin 配置。

Example 4.19. 配置 Maven 裝配描述符

<project>

  [...]

  <build>

    <plugins>

      <plugin>

        <artifactId>maven-assembly-plugin</artifactId>

        <configuration>

          <descriptorRefs>

            <descriptorRef>jar-with-dependencies</descriptorRef>

          </descriptorRefs>

        </configuration>

      </plugin>

    </plugins>

  </build>

  [...]

</project>

 

添加好這些配置以後,你可以通過運行 mvn assembly:assembly 來構建這個裝配。

mvn install assembly:assembly

...

[INFO] [jar:jar]

[INFO] Building jar: ~/examples/simple-weather/target/simple-weather-1.0.jar

[INFO] [assembly:assembly]

[INFO] Processing DependencySet (output=)

[INFO] Expanding: /

       .m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar into /

       /tmp/archived-file-set.1437961776.tmp

[INFO] Expanding: .m2/repository/commons-lang/commons-lang/2.1/commons-lang-2.1.jar /

       into /tmp/archived-file-set.305257225.tmp

... (Maven Expands all dependencies into a temporary directory) ...

[INFO] Building jar: /

       ~/examples/simple-weather/target/simple-weather-1.0-jar-with-dependencies.jar

在 target/simple-weather-1.0-jar-with-dependencies.jar 裝配好之後,我們可以在命令行重新運行 Main 類。在你項目的基礎目錄下運行以下命令:

cd target

java -cp simple-weather-1.0-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 10002

0    INFO  YahooRetriever  - Retrieving Weather Data

221  INFO  YahooParser  - Creating XML Reader

399  INFO  YahooParser  - Parsing XML Response

474  INFO  WeatherFormatter  - Formatting Weather Data

*********************************

 Current Weather Conditions for:

  New York, NY, US

 

 Temperature: 44

   Condition: Fair

    Humidity: 40

  Wind Chill: 40

*********************************

jar-with-dependencies 格式創建一個包含所有 simple-weather 項目的二進制代碼以及所有依賴解壓出來的二進制代碼的 JAR 文件。這個略微非常規的格式產生了一個 9 MiB 大小的 JAR 文件,包含了大概 5290 個類。 但是它確實給那些使用 Maven 開發的應用程序提供了一個易於分發的格式。本書的後面,我們會說明如何創建一個自定義的裝配描述符來生成一個更標準的分發包。

                   Chapter 5. 一個簡單的Web應用

5.1. 介紹

5.1.1. 下載本章樣例

5.2. 定義這個簡單的Web應用

5.3. 創建這個簡單的Web應用

5.4. 配置Jetty插件

5.5. 添加一個簡單的Servlet

5.6. 添加J2EE依賴

5.7. 小結

                   5.1. 介紹

本章我們使用 Maven Archetype 插件創建一個簡單的 web 應用程序。我們將會在一個名爲 Jetty 的 Servlet 容器中運行這個 web 應用程序,同時添加一些依賴,編寫一個簡單的 Servlet,並且生成一個 WAR 文件。 本章最後,你將能夠開始使用 Maven 來提高你開發web 應用程序的速度。

                        5.1.1. 下載本章樣例

本章的樣例是通過 Maven Archetype 插件生成的。雖然沒有樣例源碼你也應該能夠理解這個開發過程,但還是推薦你下載樣例源碼作爲參考。 本章的樣例項目包含在本書的樣例代碼中,你可以從兩個地方下載,http://www.sonatype.com/book/mvn-examples-1.0.zip或者 http://www.sonatype.com/book/mvn-examples-1.0.tar.gz 。解開存檔文件至任意目錄,然後到 ch05/ 目錄。 在 ch05/ 目錄你會看到一個名爲 simple-webapp/ 的目錄,它包含了本章開發出來的 Maven 項目。如果你想要在瀏覽器裏看樣例代碼,訪問http://www.sonatype.com/book/examples-1.0 ,然後點擊 ch05/ 目錄。

                   5.2. 定義這個簡單的Web應用

我們已經有意的使本章關注於一個簡單 Web 應用(POWA)—— 一個 servlet 和一個 JSP 頁面。 在接下來的二十多頁中,我們不會告訴你如何開發你的 Struts 2,Tapesty,Wicket,JSF,或者 Waffle 應用,我們也不會涉及到集成諸如 Plexus,Guice 或者 Spring Framework 之類的 IoC 容器。 本章的目標是展示給你看開發 web 應用的時候 Maven 提供的基本設備,不多,也不少。本書的後面,我們將會看一下開發兩個 web 應用,一個使用了 Hibernate, Velocity 和 Spring Framework,另外一個使用了 Plexus。

                   5.3. 創建這個簡單的Web應用

創建你的 web 應用程序項目,運行 mvn archetype:create ,加上參數 artifactId 和 groupId。 指定 archetypeArtifactId 爲maven-archetype-webapp。 如此便創建了恰到好處的目錄結構和 Maven POM。

~/examples$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch05 /

                                                             -DartifactId=simple-webapp /

                                                             -DpackageName=org.sonatype.mavenbook /

                                                             -DarchetypeArtifactId=maven-archetype-webapp

[INFO] [archetype:create]

[INFO] ----------------------------------------------------------------------------

[INFO] Using following parameters for creating Archetype: maven-archetype-webapp:RELEASE

[INFO] ----------------------------------------------------------------------------

[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch05

[INFO] Parameter: packageName, Value: org.sonatype.mavenbook

[INFO] Parameter: basedir, Value: ~/examples

[INFO] Parameter: package, Value: org.sonatype.mavenbook

[INFO] Parameter: version, Value: 1.0-SNAPSHOT

[INFO] Parameter: artifactId, Value: simple-webapp

[INFO] ********************* End of debug info from resources from generated POM *******

[INFO] Archetype created in dir: ~/examples/simple-webapp

在 Maven Archetype 插件創建好了項目之後,切換目錄至 simple-web 後看一下 pom.xml 。 你會看到如下的 XML 文檔:

Example 5.1. simple-web 項目的初始 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

         http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.sonatype.mavenbook.ch05</groupId>

  <artifactId>simple-webapp</artifactId>

  <packaging>war</packaging>

  <version>1.0-SNAPSHOT</version>

  <name>simple-webapp Maven Webapp</name>

  <url>http://maven.apache.org</url>

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

  <build>

    <finalName>simple-webapp</finalName>

  </build>

</project>

 

注意 packaging 元素包含的值是 war 。這種打包類型配置讓 Maven 以 WAR 文件的形式生成一個 web 應用。一個打包類型爲 war的項目,將會在 target/ 目錄創建一個 WAR 文件,這個文件的默認名稱是 ${artifactId}-${version}.war 。對於這個項目, 默認的 WAR 文件是 target/simple-webapp-1.0-SNAPSHOT.war 。在這個 simple-webapp 項目中,我們已經通過在項目的構建配置中加入 finalName 元素來自定義這個生成的 WAR 文件的名稱。根據 simple-webapp 的 finalName ,package 階段生成的 WAR 文件爲 target/simple-webapp.war 。

                   5.4. 配置Jetty插件

在你已經編譯,測試並且打包了你的 web 應用之後,你會想要將它部署到一個 servlet 容器中,然後測試一下由 Maven Archetype 插件創建的 index.jsp 。通常情況下,你需要下載 Jetty 或者 Apache Tomcat,解壓分發包,複製你的應用程序 WAR 文件至webapps/ 目錄,然後啓動你的容器。現在,實現同樣的目的,你不再需要做這些事情。 取而代之的是,你可以使用 Maven Jetty 插件在 Maven 中運行你的 web 應用。爲此,你需要在項目的 pom.xml 中配置 Maven Jetty 插件。在你項目的構建配置中添加如下插件元素:

Example 5.2. 配置 Jetty 插件

<project>

  [...]

  <build>

    <finalName>simple-webapp</finalName>

    <plugins>

      <plugin>

        <groupId>org.mortbay.jetty</groupId>

        <artifactId>maven-jetty-plugin</artifactId>

      </plugin>

    </plugins>

  </build>

  [...]

</project>

 

在項目的 pom.xml中配置好 Maven Jetty 插件之後,你就可以調用 Jetty 插件的 Run 目標在 Jetty Servlet 容器中啓動你的 web 應用。如下運行 mvn jetty:run :

~/examples$ mvn jetty:run

...

[INFO] [jetty:run]

[INFO] Configuring Jetty for project: simple-webapp Maven Webapp

[INFO] Webapp source directory = /

       /Users/tobrien/svnw/sonatype/examples/simple-webapp/src/main/webapp

[INFO] web.xml file = /

       /Users/tobrien/svnw/sonatype/examples/simple-webapp/src/main/webapp/WEB-INF/web.xml

[INFO] Classes = /Users/tobrien/svnw/sonatype/examples/simple-webapp/target/classes

2007-11-17 22:11:50.532::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog

[INFO] Context path = /simple-webapp

[INFO] Tmp directory =  determined at runtime

[INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml

[INFO] Web overrides =  none

[INFO] Webapp directory = /

       /Users/tobrien/svnw/sonatype/examples/simple-webapp/src/main/webapp

[INFO] Starting jetty 6.1.6rc1 ...

2007-11-17 22:11:50.673::INFO:  jetty-6.1.6rc1

2007-11-17 22:11:50.846::INFO:  No Transaction manager found - if your webapp requires one,/

       please configure one.

2007-11-17 22:11:51.057::INFO:  Started [email protected]:8080

[INFO] Started Jetty Server

當 Maven 啓動了 Jetty Servlet 容器之後,在瀏覽器中載入 URL http://localhost:8080/simple-webapp/ 。 Archetype 生成的簡單頁面index.jsp 沒什麼價值;它包含了一個文本爲“Hello World!”的二級標題。 Maven 認爲 web 應用程序的文檔根目錄爲src/main/webapp 。這個目錄就是存放 index.jsp 的目錄 。 index.jsp 的內容爲 Example 5.3, “src/main/webapp/index.jsp 的內容”

Example 5.3. src/main/webapp/index.jsp 的內容

<html>

  <body>

    <h2>Hello World!</h2>

  </body>

</html>

 

在 src/main/webapp/WEB-INF 目錄中我們會找到可能是最小的 web 應用程序描述符 web.xml。

Example 5.4. src/main/webapp/WEB-INF/web.xml 的內容

<!DOCTYPE web-app PUBLIC

 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >

 

<web-app>

  <display-name>Archetype Created Web Application</display-name>

</web-app>

 

                   5.5. 添加一個簡單的Servlet

一個只有一個單獨的 JSP 頁面而沒有任何配置好的 servlet 的 web 應用程序基本是沒用的。 讓我們爲這個應用添加一個簡單的 servlet,同時爲 pom.xml 和 web.xml 做些改動以支持這個變化。 首先,我們需要在目錄 src/main/java 下創建一個名爲org.sonatype.mavenbook.web 的新的包。

mkdir -p src/main/java/org/sonatype/mavenbook/web

cd src/main/java/org/sonatype/mavenbook/web

包創建好之後,切換目錄至 src/main/java/org/sonatype/mavenbook/web ,創建一個名爲 SimpleServlet.java 的 servlet類,代碼如下:

Example 5.5. SimpleServlet 類

package org.sonatype.mavenbook.web;

 

import java.io.*;

import javax.servlet.*;                                                         

import javax.servlet.http.*;

 

public class SimpleServlet extends HttpServlet {

    public void doGet(HttpServletRequest request,

                      HttpServletResponse response)

        throws ServletException, IOException {

   

        PrintWriter out = response.getWriter();

        out.println( "SimpleServlet Executed" );

        out.flush();

        out.close();

    }

}

 

我們的 SimpleServlet 僅此而已,一個往響應 Writer 打印一條簡單信息的 servlet 。爲了把這個 servlet 添加到你的 web 應用,並且使其與請求路徑匹配,需要添加如下的 servlet 和 servlet-mapping 元素至你項目的 web.xml 文件。 文件 web.xml 可以在目錄src/main/webapp/WEB-INF 中找到。

Example 5.6. 匹配 Simple Servlet

<!DOCTYPE web-app PUBLIC

 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >

 

<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>

    <servlet-name>simple</servlet-name>

    <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class>

  </servlet>

  <servlet-mapping>

    <servlet-name>simple</servlet-name>

    <url-pattern>/simple</url-pattern>

  </servlet-mapping>

</web-app>

 

一切文件就緒,準備測試這個 servlet 。 src/main/java 下的類,以及 web.xml 已經被更新了。在啓動 Jetty 插件之前,運行 mvn compile 以編譯你的項目:

~/examples$ mvn compile

...

[INFO] [compiler:compile]

[INFO] Compiling 1 source file to ~/examples/ch05/simple-webapp/target/classes

[INFO] ------------------------------------------------------------------------

[ERROR] BUILD FAILURE

[INFO] ------------------------------------------------------------------------

[INFO] Compilation failure

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[4,0] /

  package javax.servlet does not exist

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[5,0] /

  package javax.servlet.http does not exist

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[7,35] /

  cannot find symbol

  symbol: class HttpServlet

  public class SimpleServlet extends HttpServlet {

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[8,22] /

  cannot find symbol

  symbol  : class HttpServletRequest

  location: class org.sonatype.mavenbook.web.SimpleServlet

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[9,22] /

  cannot find symbol

  symbol  : class HttpServletResponse

  location: class org.sonatype.mavenbook.web.SimpleServlet

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[10,15] /

  cannot find symbol

  symbol  : class ServletException

  location: class org.sonatype.mavenbook.web.SimpleServlet

編譯失敗了,因爲你的 Maven 項目沒有對 Servlet API 的依賴。 在下一節,我們會爲你項目的 POM 添加 Servlet API 。

                   5.6. 添加J2EE依賴

爲了編寫一個 servlet ,我們需要添加 Servlet API 作爲項目依賴。 Servlet 規格說明是一個 JAR 文件,它能從 Sun Microsystems 的站點下載到 http://java.sun.com/products/servlet/download.html 。JAR 文件下載好之後你需要把它安裝到位於 ~/.m2/repository 的Maven 本地倉庫。你必須爲所有 Sun Microsystems 維護的 J2EE API 重複同樣的過程,包括 JNDI, JDBC, Servlet, JSP, JTA, 以及其它。如果你不想這麼做因爲覺得這樣太無聊了,其實不只有你這麼認爲。 幸運的是,有一種更簡單的方法來下載所有這些類庫並安裝到本地倉庫 —— Apache Geronimo 的獨立的開源實現。

很多年以來,獲取 Servlet 規格說明 JAR 文件的唯一方式是從 Sun Microsystems 下載。你必須到 Sun 的 web 站點,同意並且點擊它的許可證協議,這樣才能訪問 Servlet JAR。這是必須的,因爲 Sun 的規格說明 JAR 文件並沒有使用一個允許再次分發的許可證。很多年來編寫一個 Servlet 或者使用 JDBC 之前你必須手工下載 Sun 的構件。 這很乏味並且令人惱火,直到 Apache Geronimo 項目創建了很多通過 Sun 認證的企業級規格說明實現。 這些規格說明 JAR 是按照 Apache 軟件許可證版本 2.0 發佈的,該許可證允許對源代碼和二進制文件進行免費的再次分發。現在,對你的程序來說,從 Sun Microsystems 下載的 Servlet API JAR 和從 Apache Geronimo 項目下載的 JAR 沒什麼大的差別。 它們同樣都通過了 Sun Microsystems 的嚴格的一個測試兼容性工具箱(TCK)。

添加像 JSP API 或者 Servlet API 這樣的依賴現在很簡單明瞭了,不再需要你從 web 站點手工下載 JAR 文件然後再安裝到本地倉庫。關鍵是你必須知道去哪裏找,使用什麼 groupId, artifactId,和 version 來引用恰當的 Apache Geronimo 實現。給 pom.xml添加如下的依賴元素以添加對 Servlet 規格說明 API 的依賴。.

Example 5.7. 添加 Servlet 2.4 規格說明作爲依賴

<project>

  [...]

  <dependencies>

    [...]

    <dependency>

      <groupId>org.apache.geronimo.specs</groupId>

      <artifactId>geronimo-servlet_2.4_spec</artifactId>

      <version>1.1.1</version>

      <scope>provided</scope>

    </dependency>

  </dependencies>

  [...]

</project>

 

所有 Apache Geronimo 規格說明的實現的 groupId 都是 org.apache.geronimo.specs 。這個 artifactId 包含了你熟悉的規格說明的版本號;例如,如果你要引入 Servlet 2.3 規格說明,你將使用的 artifactId 是 geronimo-servlet_2.3_spec ,如果你想要引入 Servlet 2.4 規格說明,那麼你的 artifactId 將會是 geronimo-servlet_2.4_spec 。你必須看一下 Maven 的公共倉庫來決定使用什麼版本。最好使用某個規格說明實現的最新版本。 如果你在尋找某個特定的 Sun 規格說明對應的 Apache Geronimo 項目,我們已經在附錄歸納了一個可用規格說明的列表。

這裏還有必要指出的是我們的這個依賴使用了 provided 範圍。這個範圍告訴 Maven jar 文件已經由容器“提供”了,因此不再需要包含在 war 中。

如果你對在這個簡單 web 應用編寫自定義 JSP 標籤感興趣,你將需要添加對 JSP 2.0 規格說明的依賴。使用以下配置來加入這個依賴。

Example 5.8. 添加 JSP 2.0 規格說明作爲依賴

<project>

  [...]

  <dependencies>

    [...]

    <dependency>

      <groupId>org.apache.geronimo.specs</groupId>

      <artifactId>geronimo-jsp_2.0_spec</artifactId>

      <version>1.1</version>

      <scope>provided</scope>

    </dependency>

  </dependencies>

  [...]

</project>

 

在添加好這個 Servlet 規格說明依賴之後,運行 mvn clean install ,然後運行 mvn jetty:run 。

[tobrien@t1 simple-webapp]$ mvn clean install

...

[tobrien@t1 simple-webapp]$ mvn jetty:run

[INFO] [jetty:run]

...

2007-12-14 16:18:31.305::INFO:  jetty-6.1.6rc1

2007-12-14 16:18:31.453::INFO:  No Transaction manager found - if your webapp requires one,/

           please configure one.

2007-12-14 16:18:32.745::INFO:  Started [email protected]:8080

[INFO] Started Jetty Server

到此爲止,你應該能夠獲取 SimpleServlet 的輸出。在命令行,你可以使用 curl 在標準輸出打印 servlet 的輸出。

~/examples$ curl http://localhost:8080/simple-webapp/simple

SimpleServlet Executed

                   5.7. 小結

閱讀完本章之後,你應該能夠啓動一個簡單的 web 應用程序。本章並沒有詳細描述用很多不同的方式來創建一個完整的 web 引用,其它章節對那些包含很多流行 web 框架和技術的項目提供了更全面的介紹

                   Chapter 6. 一個多模塊項目

6.1. 簡介

6.1.1. 下載本章樣例

6.2. simple-parent 項目

6.3. simple-weather 模塊

6.4. simple-webapp 模塊

6.5. 構建這個多模塊項目

6.6. 運行Web應用

                   6.1. 簡介

本章,我們創建一個結合了前兩章樣例的多模塊項目。???中開發的simple-weather代碼將會與Chapter 5, 一個簡單的Web應用中定義的simple-webapp結合以創建一個新的web應用,它獲取天氣預報信息然後顯示在web頁面上。本章最後,你能將夠使用Maven開發複雜的,多模塊項目。

                        6.1.1. 下載本章樣例

該樣例中開發的多模塊項目包含了???Chapter 5, 一個簡單的Web應用中項目的修改的版本,我們不會再使用Maven Archetype插件來生成這個多模塊項目。我們強烈建議當你在閱讀本章內容的時候,下載樣例代碼作爲一個補充參考。本章的樣例項目包含在本書的樣例代碼中,你可以從兩個地方下載,http://www.sonatype.com/book/mvn-examples-1.0.zip或者http://www.sonatype.com/book/mvn-examples-1.0.tar.gz。解開存檔文件至任意目錄,然後到ch06/目錄。在ch06/目錄你會看到一個名爲simple-parent/的目錄,它包含了本章開發出來的多模塊Maven項目。在這個simple-parent/項目目錄中,你會看到一個pom.xml,以及兩個子模塊目錄simple-weather/和simple-webapp/。如果你想要在瀏覽器裏看樣例代碼,訪問http://www.sonatype.com/book/examples-1.0,然後點擊ch06/目錄。

                   6.2. simple-parent 項目

一個多模塊項目通過一個父POM引用一個或多個子模塊來定義。在simple-parent/目錄中你能找到一個父POM(也稱爲頂層POM)爲simple-parent/pom.xml。

Example 6.1. simple-parent 項目的 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

 

  <groupId>org.sonatype.mavenbook.ch06</groupId>

  <artifactId>simple-parent</artifactId>

  <packaging>pom</packaging>

  <version>1.0</version>

  <name>Chapter 6 Simple Parent Project</name>

 

  <modules>

    <module>simple-weather</module>

    <module>simple-webapp</module>

  </modules>

 

  <build>

    <pluginManagement>

      <plugins>

        <plugin>

          <groupId>org.apache.maven.plugins</groupId>

          <artifactId>maven-compiler-plugin</artifactId>

          <configuration>

            <source>1.5</source>

            <target>1.5</target>

          </configuration>

        </plugin>

      </plugins>

   </pluginManagement>

  </build>

 

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

 

 

注意simple-parent定義了一組Maven座標:groupId是org.sonatype.mavenbook,artifactId是simple-parent,version是1.0。這個父項目不像之前的項目那樣創建一個JAR或者一個WAR,它僅僅是一個引用其它Maven項目的POM。像simple-parent這樣僅僅提供項目對象模型的項目,正確的的打包類型是pom。pom.xml中下一部分列出了項目的子模塊。這些模塊在modules元素中定義,每個modules元素對應了一個simple-parent/目錄下的子目錄。Maven知道去這些子目錄尋找pom.xml文件,並且,在構建的simp-parent的時候,它會將這些子模塊包含到要構建的項目中。

最後,我們定義了一些將會被所有子模塊繼承的設置。simple-parent的build部分配置了所有Java編譯的目標是Java 5 JVM。因爲compiler插件默認綁定到了生命週期,我們就可以使用pluginManagement部分來配置。我們將會在後面的章節詳細討論pluginManagement,區分爲默認的插件提供配置和真正的綁定插件是很容易的。dependencies元素將JUnit 3.8.1添加爲一個全局的依賴。build配置和dependencies都會被所有的子模塊繼承。使用POM繼承允許你添加一些全局的依賴如JUnit和Log4J。

                   6.3. simple-weather 模塊

我們要看的第一個子模塊是simple-weather子模塊。這個子模塊包含了所有用來與Yahoo! weather信息源交互的類。

Example 6.2. simple-weather 模塊的 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <parent>

    <groupId>org.sonatype.mavenbook.ch06</groupId>

    <artifactId>simple-parent</artifactId>

    <version>1.0</version>

  </parent>

  <artifactId>simple-weather</artifactId>

  <packaging>jar</packaging>

 

  <name>Chapter 6 Simple Weather API</name>

 

  <build>

    <pluginManagement>

      <plugins>

        <plugin>

          <groupId>org.apache.maven.plugins</groupId>

          <artifactId>maven-surefire-plugin</artifactId>

          <configuration>

            <testFailureIgnore>true</testFailureIgnore>

          </configuration>

        </plugin>

      </plugins>

    </pluginManagement>

  </build>

 

  <dependencies>

    <dependency>

      <groupId>log4j</groupId>

      <artifactId>log4j</artifactId>

      <version>1.2.14</version>

    </dependency>

    <dependency>

      <groupId>dom4j</groupId>

      <artifactId>dom4j</artifactId>

      <version>1.6.1</version>

    </dependency>

    <dependency>

      <groupId>jaxen</groupId>

      <artifactId>jaxen</artifactId>

      <version>1.1.1</version>

    </dependency>

    <dependency>

      <groupId>velocity</groupId>

      <artifactId>velocity</artifactId>

      <version>1.5</version>

    </dependency>

    <dependency>

      <groupId>org.apache.commons</groupId>

      <artifactId>commons-io</artifactId>

      <version>1.3.2</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

 

在simple-weather的pom.xml文件中我們看到該模塊使用一組Maven座標引用了一個父POM。simple-weather的父POM通過一個值爲org.sonatype.mavenbook的groupId,一個值爲simple-parent的artifactId,以及一個值爲1.0的version來定義。注意子模塊中我們不再需要重新定義groupId和version,它們都從父項目繼承了。

Example 6.3. WeatherService 類

package org.sonatype.mavenbook.weather;

 

import java.io.InputStream;

 

public class WeatherService {

 

    public WeatherService() {}

 

    public String retrieveForecast( String zip ) throws Exception {

        // Retrieve Data

        InputStream dataIn = new YahooRetriever().retrieve( zip );

 

        // Parse Data

        Weather weather = new YahooParser().parse( dataIn );

 

        // Format (Print) Data

        return new WeatherFormatter().format( weather );

    }

}

 

WeatherService類在src/main/java/org/sonatype/mavenbook/weather中定義,它簡單的調用???中定義的三個對象。在本章的樣例中,我們正創建一個單獨的項目,它包含了將會在web應用項目中被引用的service對象。這是一個在企業級Java開發中常見的模型,通常一個複雜的應用包含了不止一個的簡單web應用。你可能擁有一個企業應用,它包含了多個web應用,以及一些命令行應用。通常你會想要 重構那些通用的邏輯至一個service類,以被很多項目重用。這就是我們創建WeatherService類的理由,在此之後,你就能看到simple-webapp項目是如何引用在simple-weather中定義的service對象。

retrieveForecast()方法接受一個包含郵政編碼的String。之後這個郵政編碼參數被傳給YahooRetriever的retrieve()方法,後者從Yahoo! Weather獲取XML。從YahooRetriever返回的XML被傳給YahooParser的parse()方法,後者繼而又返回一個Weather對象。之後這個Weather對象被WeatherFormatter格式化成一個像樣的String。

                   6.4. simple-webapp 模塊

simple-webapp模塊是在simple-parent項目中引用的第二個子模塊。這個web項目依賴於simple-weather模塊,並且包含了一些用來展示從Yahoo! Weather服務查詢到的結果的servlet。

Example 6.4. simple-webapp 模塊的 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <parent>

    <groupId>org.sonatype.mavenbook.ch06</groupId>

    <artifactId>simple-parent</artifactId>

    <version>1.0</version>

  </parent>

 

  <artifactId>simple-webapp</artifactId>

  <packaging>war</packaging>

  <name>simple-webapp Maven Webapp</name>

  <dependencies>

    <dependency>

      <groupId>org.apache.geronimo.specs</groupId>

      <artifactId>geronimo-servlet_2.4_spec</artifactId>

      <version>1.1.1</version>

    </dependency>

    <dependency>

      <groupId>org.sonatype.mavenbook.ch06</groupId>

      <artifactId>simple-weather</artifactId>

      <version>1.0</version>

    </dependency>

  </dependencies>

  <build>

    <finalName>simple-webapp</finalName>

    <plugins>

      <plugin>

        <groupId>org.mortbay.jetty</groupId>

        <artifactId>maven-jetty-plugin</artifactId>

      </plugin>

    </plugins>

  </build>

</project>

 

simple-weather模塊定義了一個十分簡單的servlet,它從HTTP請求讀取一個郵政編碼,調用Example 6.3, “WeatherService 類”中展示的WeatherService,然後將結果打印至HTTP響應的Writer。

Example 6.5. simple-webapp 的 WeatherServlet

package org.sonatype.mavenbook.web;

 

import org.sonatype.mavenbook.weather.WeatherService;

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class WeatherServlet extends HttpServlet {

    public void doGet(HttpServletRequest request,

                      HttpServletResponse response)

        throws ServletException, IOException {

        String zip = request.getParameter("zip" );

        WeatherService weatherService = new WeatherService();

        PrintWriter out = response.getWriter();

        try {

            out.println( weatherService.retrieveForecast( zip ) );

        } catch( Exception e ) {

            out.println( "Error Retrieving Forecast: " + e.getMessage() );

        }

        out.flush();

        out.close();

    }

}

 

在WeatherServlet中,我們初始化了一個在simple-weather中定義的WeatherService類的實例。在請求參數中提供的郵政編碼被傳給retrieveForecast()方法,並且返回結果被打印至HTTP響應的Writer。

最後,src/main/webapp/WEB-INF目錄下的web.xml將所有這一切綁在一起。web.xml中的servlet和servlet-mapping元素將路徑/weather匹配至WeatherServlet。

Example 6.6. simple-webapp 的 web.xml

<!DOCTYPE web-app PUBLIC

 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >

 

<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>

    <servlet-name>simple</servlet-name>

    <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class>

  </servlet>

  <servlet>

    <servlet-name>weather</servlet-name>

    <servlet-class>org.sonatype.mavenbook.web.WeatherServlet</servlet-class>

  </servlet>

  <servlet-mapping>

    <servlet-name>simple</servlet-name>

    <url-pattern>/simple</url-pattern>

  </servlet-mapping>

  <servlet-mapping>

    <servlet-name>weather</servlet-name>

    <url-pattern>/weather</url-pattern>

  </servlet-mapping>

</web-app>

 

                   6.5. 構建這個多模塊項目

既然simple-weather項目包含了所有與Yahoo! Weather服務交互的代碼,以及simple-webapp項目包含了一個簡單的servlet,是時間將這個應用編譯並打包成一個WAR文件了。爲此,你會想要以合適的順序編譯並安裝這兩個項目;以爲simple-webapp依賴於simple-weather,simple-weather的JAR需要在simple-webapp項目被編譯之前就被創建好。爲此,你需要從simple-parent項目運行mvn clean install命令。

~/examples/ch06/simple-parent$ mvn clean install

[INFO] Scanning for projects...

[INFO] Reactor build order:

[INFO]   Simple Parent Project

[INFO]   simple-weather

[INFO]   simple-webapp Maven Webapp

[INFO] ----------------------------------------------------------------------

[INFO] Building simple-weather

[INFO]    task-segment: [clean, install]

[INFO] ----------------------------------------------------------------------

[...]

[INFO] [install:install]

[INFO] Installing simple-weather-1.0.jar to simple-weather-1.0.jar

[INFO] ----------------------------------------------------------------------

[INFO] Building simple-webapp Maven Webapp

[INFO]    task-segment: [clean, install]

[INFO] ----------------------------------------------------------------------

[...]

[INFO] [install:install]

[INFO] Installing simple-webapp.war to simple-webapp-1.0.war

[INFO]

[INFO] ----------------------------------------------------------------------

[INFO] Reactor Summary:

[INFO] ----------------------------------------------------------------------

[INFO] Simple Parent Project ............................... SUCCESS [3.041s]

[INFO] simple-weather ...................................... SUCCESS [4.802s]

[INFO] simple-webapp Maven Webapp .......................... SUCCESS [3.065s]

[INFO] ----------------------------------------------------------------------

當Maven執行一個帶有子模塊的項目的時候,Maven首先載入父POM,然後定位所有的子模塊POM。Maven然後將所有這些項目的POM放入到一個稱爲Maven 反應堆(Reactor)的東西中,由它負責分析模塊之間的依賴關係。這個反應堆處理組件的排序,以確保相互獨立的模塊能以適當的順序被編譯和安裝。

Note

除非需要做更改,反應堆一直維持定義在POM中的模塊的順序。爲此一個有幫助的思維模型是,那些依賴於兄弟項目的項目在列表中被“向下按”,直到依賴順序被滿足。在很少的情況下,重新安排你構建的模塊順序可能很方便——例如你想要一個頻繁的不穩定的模塊接近構建的開端。

一旦反應堆解決了項目構建的順序,Maven就會在多模塊構建中爲每個模塊執行特定的目標。本例中,你能看到Maven在simple-webapp之前構建了simple-weather,爲每個子模塊執行了mvn clean install

Note

當你在命令行運行Maven的時候你經常會在任何其它生命週期階段前指定clean生命週期階段。當你指定clean,你就確認了在編譯和打包一個應用之前,Maven會移除舊的輸出。運行clean不是必要的,但這是一個確保你正執行“乾淨構建”的十分有用的預防措施。

                   6.6. 運行Web應用

一旦這個多模塊項目已經通過從父項目simple-project執行mvn clean install行安裝好了,你可以切換目錄至simple-webapp項目,然後運行Jetty插件的Run目標。

~/examples/ch06/simple-parent/simple-webapp $ mvn jetty:run

[INFO] ----------------------------------------------------------------------

[INFO] Building simple-webapp Maven Webapp

[INFO]    task-segment: [jetty:run]

[INFO] ----------------------------------------------------------------------

[...]

[INFO] [jetty:run]

[INFO] Configuring Jetty for project: simple-webapp Maven Webapp

[...]

[INFO] Webapp directory = ~/examples/ch06/simple-parent//

                          simple-webapp/src/main/webapp

[INFO] Starting jetty 6.1.6rc1 ...

2007-11-18 1:58:26.980::INFO:  jetty-6.1.6rc1

2007-11-18 1:58:26.125::INFO:  No Transaction manager found

2007-11-18 1:58:27.633::INFO:  Started [email protected]:8080

[INFO] Started Jetty Server

Jetty啓動之後,在瀏覽器載入http://localhost:8080/simple-webapp/weather?zip=01201,你應該看到格式化的天氣輸出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章