項目管理: Maven 讓事情變得簡單

目前,絕大多數開發人員都把 Ant 當作 Java 編程項目的標準構建工具。遺憾的是,Ant 的項目管理工具(作爲 make 的替代工具)不能滿足絕大多數開發人員的需要。通過檢查 Ant 構建文件,很難發現項目的相關性信息和其它元信息(如開發人員/擁有者、版本或站點主頁)。

Maven 除了以程序構建能力爲特色之外,還提供 Ant 所缺少的高級項目管理工具。由於 Maven 的缺省構建規則有較高的可重用性,所以常常用兩三行 Maven 構建腳本就可以構建簡單的項目,而使用 Ant 則需要十幾行。事實上,由於 Maven 的面向項目的方法,許多 Apache Jakarta 項目現在使用 Maven,而且公司項目採用 Maven 的比例在持續增長。

一、MAVEN VS ANT

那麼,Maven 和 Ant 有什麼不同呢?在回答這個問題以前,我要強調一點:Maven 和 Ant 針對構建問題的兩個不同方面。Ant 爲 Java 技術開發項目提供跨平臺構建任務。Maven 本身描述項目的高級方面,它從 Ant 借用了絕大多數構建任務。因此,由於 Maven 和 Ant 代表兩個差異很大的工具,所以我將只說明這兩個工具的等同組件之間的區別,如表 1 所示。


表1  maven vs ant

  Maven Ant
標準構建文件 project.xml 和 maven.xml build.xml
特性處理順序
  1. ${maven.home}/bin/driver.properties
  2. ${project.home}/project.properties
  3. ${project.home}/build.properties
  4. ${user.home}/build.properties
  5. 通過 -D 命令行選項定義的系統特性
最後一個定義起決定作用。
  1. 通過 -D 命令行選項定義的系統特性
  2. <property> 任務裝入的特性
第一個定義最先被處理。
構建規則 構建規則更爲動態(類似於編程語言);它們是基於 Jelly 的可執行 XML。 構建規則或多或少是靜態的,除非使用 <script> 任務。(請參閱 參考資料以獲得相關教程。)
擴展語言 插件是用 Jelly(XML)編寫的。 插件是用 Java 語言編寫的。
構建規則可擴展性 通過定義 <preGoal><postGoal> 使構建 goal 可擴展。 構建規則不易擴展;可通過使用 <script> 任務模擬 <preGoal><postGoal> 所起的作用。


二、maven的主要組件


既然您瞭解了 Maven 和 Ant 之間的區別,讓我們來研究 Maven 的主要組件,如圖 1 所示

圖1:maven的主要組件


Maven 的主要組件

1、項目對象模型

項目對象模型(Project Object Model,POM)描述項目的各個方面。儘管對於 POM 的物理表示沒有內在的限制,但 Maven 開發人員通常使用一個 XML 項目文件(project.xml)。該 XML 文件格式由位於 Maven 安裝目錄中的 XML 模式(maven-project.xsd)定義。

通常,project.xml 文件由三個主要部分組成:

  • 項目管理部分包括項目的組織、開發人員名單、源代碼位置和錯誤跟蹤系統 URL 等信息。
  • 項目相關性部分包括關於項目相關性的信息。當前 Maven 實現(1.0 beta 測試版 8)僅支持 JAR 文件相關性。
  • 項目構建和報告部分包含項目構建信息(如源代碼目錄、單元測試用例目錄)和要在構建中生成的報告。

清單 1 顯示了帶註釋的樣本 project.xml 文件。因爲 project.xml 文件中的許多元素都是可選的,所以,隨着您對 Maven 理解的加深,可以逐步使用不同的 Maven 特性。注:在以下代碼中,可選的元素都以“可選的(OPTIONAL)”標明。

主文檔包含項目的唯一標識和組標識。事實證明,當項目包括多個子項目時,組標識非常有用。所有的子項目應共享同一組標識,但每個子項目應有不同的 <id>


清單1:主project.xml框架

<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- A project file's root element -->
<project>
  <!-- The POM version. This tag is currently unused. -->
  <pomVersion>3</pomVersion>
  <!-- A project group id. If present, the id serves as the project's
        directory name in the repository -->
  <groupId>crayola-group</groupId>
  <!-- A unique project identifier. The project identifier and its
        version number often generate file/directory names during the
        build. For example, a project JAR file follows the
        <id>-<version> naming convention. -->
  <id>crayola</id>
  <!-- A short name for the project -->
  <name>Crayola Professional</name>
  <!-- The project version number. Maven does not enforce a particular
        version numbering scheme. -->
  <currentVersion>0.0.1</currentVersion>
  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project management section                                -->
  <!-- 
---------------------------------------------------------------- -->
  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project dependency section                                -->
  <!-- 
---------------------------------------------------------------- -->
  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project build and reports section                         -->
  <!-- 
---------------------------------------------------------------- -->
  ...
</project>

項目管理部分(如清單 2 所示)主要包括可選項。在此部分中指定開發人員名單(帶有正確的標識),當您希望獲得更改日誌(Change Log)報告和開發活動(Development Activity)報告時尤其要這麼做。


清單2:項目管理部分

  
...
<!-- 
---------------------------------------------------------------- -->
  <!-- Project management section                                -->
  <!-- 
---------------------------------------------------------------- -->
  <!-- Details of the organization holding the project. Only the name
        is required. -->
  <organization>
    <name>Markers Inc.</name>
    <url>http://w3.markers.com/</url>
    
<logo>http://w3.markers.com/logo/company-logo.gif</logo>
  </organization>
  <!-- (OPTIONAL) Year of inception -->
  <inceptionYear>2003</inceptionYear>
  <!-- (OPTIONAL) Project main package -->
  <package>com.markers.crayola.*</package>
  <!-- (OPTIONAL) Project logo picture (URL) -->
  <logo>http://w3.markers.com/logo/crayola.gif</logo>
  <!-- (OPTIONAL) GUMP repository ID. Useful only if you use GUMP. -->
  <gumpRepositoryId>crayola</gumpRepositoryId>
  <!-- (OPTIONAL) Project description -->
  <description>...</description>
  <!-- (OPTIONAL) Short project description -->
  <shortDescription>...</shortDescription>
  <!-- (OPTIONAL) Project site URL -->
  <url>http://w3.markers.com/crayola</url>
  <!-- (OPTIONAL) Issue-tracking system URL -->
  
<issueTrackingUrl>http://w3.markers.com/jira/crayola</issueTrackingUrl>
  <!-- (OPTIONAL) Project site address. -->
  <siteAddress>w3.markers.com</siteAddress>
  <!-- (OPTIONAL) Project-site deploy directory (physical location) -->
  <siteDirectory>/www/crayola/site/</siteDirectory>
  <!-- (OPTIONAL) Project distribution directory (physical location) -->
  
<distributionDirectory>/www/crayola/builds/</distributionDirectory>
  <!-- (OPTIONAL) Project source-repository information -->
  <repository>
    
<connection>
scm:cvs:pserver:[email protected]:/home/cvspublic:crayola
</connection>
    <url>http://cvs.markers.com/viewcvs/crayola/</url>
  </repository>
  <!-- (OPTIONAL) Mailing list information -->
  <mailingLists>
    <mailingList>
      <name>Dev List</name>
      
<subscribe>[email protected]</subscribe>
      
<unsubscribe>[email protected]</unsubscribe>
    </mailingList>
    ...
  </mailingLists>
  <!-- Developers involved in this project -->
  <developers>
    <developer>
      <name>John Smith</name>
      <id>jsmith</id>
      <email>[email protected]</email>
    </developer>
    ...
  </developers>

將清單 3 中的信息與一箇中央構件資源庫一起使用,將消除幾個常見的構建問題(包括錯誤配置的 CLASSPATH 或相關性-版本不匹配)


清單3:項目相關性部分

  <!-- 
---------------------------------------------------------------- -->
  <!-- Project dependency section                                -->
  <!-- 
---------------------------------------------------------------- -->
  <dependencies>
    <!-- This project depends on the JAR file "commons-beanutils-1.5.jar"
          in the Maven repository's commons-beanutils/jars subdirectory
          (more about repository later). -->
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.5</version>
    </dependency>
    <!-- This project depends on the JAR file "commons-lib-2.1.jar" in
          the Maven repository's markers/jars subdirectory. -->
    <dependency>
      <groupId>markers</groupId>
      <artifactId>commons-lib</artifactId>
      <version>2.1</version>
    </dependency>
  </dependencies>

項目構建和報告部分(如清單 4 所示)包含用於配置某些 Maven 插件的重要構建和報告信息。例如,可以配置 Maven 在站點文檔生成時包含還是排除某些報告


清單4:項目構建部分

  ...
  <!-- 
---------------------------------------------------------------- -->
  <!-- Project build and reports section                         -->
  <!-- 
---------------------------------------------------------------- -->
  <build>
    <!-- (OPTIONAL) Build notification email address. -->
    <nagEmailAddress>[email protected]</nagEmailAddress>
    <!-- (OPTIONAL) Defines where the Java source resides. -->
    <sourceDirectory>src/java</sourceDirectory>
    <!-- (OPTIONAL) Defines where the Java source for unit test-cases
          resides. -->
    
<unitTestSourceDirectory>test/java</unitTestSourceDirectory>
    <!-- (OPTIONAL) Unit test-case file pattern. -->
    <unitTest>
      <includes>
        <include>**/*Test.java</include>
      </includes>
    </unitTest>
    <!-- (OPTIONAL) Resources packaged inside the JAR file. -->
    <resources/>
    <!-- (OPTIONAL) The reports tag lets you select which reports you
          want generated for your site. In this case, only the checkstyle
          report will generate. -->
  </build>
  <reports>
    <report>
      maven-checkstyle-plugin
    </report>
  </reports>

項目依靠庫來實現其功能。例如,您的項目可能依靠 log4j 進行日誌記錄,依靠 Xalan 進行 XSLT 轉換。對於 J2EE 項目,Web 組件可能依靠 EJB 組件來執行業務操作。Maven 可以讓您用它的 POM 來表示不同的相關性。您可以用表 2 所示的標記在 project.xml 文件中描述每一個相關性。


表2:項目相關性部分:

groupId 告訴 Maven 資源庫內哪個子目錄中包含相關性文件。
artifactId 告訴 Maven 該構件的唯一標識。
version 表示相關性的版本號。
jar (可選的)表示相關性的 JAR 文件。在絕大多數情況下,可以從相關性的 <artifactId><version> 構造 JAR 文件的名稱。
type (可選的)相關性的類型;如 jar 和分發版等。缺省值是 jar。
url (可選的)相關性項目的 URL,在相關性是在因特網上找到的第三方庫時非常有用。


資源庫:

資源庫是另一個主要的 Maven 組件。在有多個項目的基於 Java 的站點中,由第三方庫組成的中央資源庫常常確保項目之間的一致性。Maven 使資源庫的結構符合標準,並且支持駐留在因特網或內部網上的遠程資源庫。清單 5 顯示了資源庫的常規結構。


清單5:資源庫

repository
|-- ant                   <-- project group ID -->
|   `-- jars              <-- artifact type, followed by 's', 
|                         <-- e.g. jars, wars, ears -->
|       `-- ant-1.5.1.jar <-- actual artifact -->
...

要創建遠程資源庫,只需將這個資源庫的目錄部署在網站中。Maven 建議使用遠程資源庫以便於集中維護,您將會最大程度地實現項目之間資源的共享。爲避免每次構建時都要下載文件,Maven 在首次下載必需的相關性資源時就自動地將其高速緩存在本地資源庫中。Maven 將表 3 中所示的特性用於遠程資源庫和本地資源庫。


表 3. 用於遠程資源庫和本地資源庫的特性


maven.repo.remote 用以逗號分隔的 URL 列表指定遠程資源庫;缺省情況下使用 http://www.ibiblio.org/maven。
maven.proxy.hostmaven.proxy.portmaven.proxy.usernamemaven.proxy.password 如果位於防火牆後面並且需要代理認證才能訪問因特網,這些設置將派上用場。
maven.repo.local 指定已下載的相關資源的高速緩存位置,缺省值爲 ${MAVEN_HOME}/repository 。在 UNIX 環境中,爲了與多個團隊共享資源庫目錄,可以爲開發人員創建一個特殊的組,然後給予這個組對資源庫目錄的讀/寫訪問權。

Maven 中的 Ant 任務

Maven 中的 goal 可在其定義中包含任何有效的 Ant 任務,這一點有助於您快速掌握 Maven 以及保護您的 Ant 投入。

goal:

Maven 中的 goal 類似 Ant 中的 target 。兩者都包含實現 goal(或 target)時會執行的任務。要在命令行中實現特定的 goal,可輸入maven <goal>

要列出所有已定義的 goal,可使用 maven -g 。表 4 列出了常用的 goal。


表 4. 常用的 goal

java:compile 編譯所有 Java 源代碼。
jar 創建已編譯的源代碼的 JAR 文件。
jar:install 將已創建的 JAR 文件發佈到本地資源庫,使得其它項目可訪問該 JAR 文件。
site 創建項目站點文檔。缺省站點文檔包含關於項目的有用信息,如包/類相關性、編碼風格一致性、源代碼交叉引用、單元測試結果或 Javadoc。要生成的報告列表是可定製的。
site:deploy 部署生成的站點文檔。

Maven 的 goal 是可擴展和可重用的。知道了這一點後,在編寫自己的 goal 之前,可先在 Maven 站點上或 ${MAVEN_HOME}/plugins 中查看 Maven 插件列表。另一個關於免費 Maven 插件的較佳資源是 SourceForge 上的 Maven 插件項目。(以上各項的鏈接可在參考資料中獲得)。

如果仍不能找到符合您要求的 goal,Maven 給您兩種選擇:

  • 編寫 <preGoal><postGoal> 來擴展標準 goal
  • 編寫自己的 goal

無論選擇哪種,都要在項目目錄中創建名爲 maven.xml 的特殊文件。清單 6 顯示了框架 maven.xml。


清單 6. 框架 maven.xml

      <?xml version="1.0" encoding="ISO-8859-1"?>
      <project xmlns:j="jelly:core">
        ...
        <goal name=...>
          ... build rules, e.g.
          <mkdir dir="${test.result.dir}"/>
          <echo>Executing JUnit tests</echo>
          ...
        </goal>
        ...
        <preGoal name=...>
          ...
        </preGoal>
        <postGoal name=...>
          ...
        </postGoal>
      </project>

熟悉 Ant 的開發人員會發現 Maven 的 goal(同樣還有 preGoalpostGoal )可在其定義中包含任何有效的 Ant 任務,這有助於快速學習 Maven 並保護在 Ant 上的投入。爲了給 Ant 任務添加動態性,Maven 也使用 Jelly 腳本編制語言。“ 基礎 Jelly 編程”用一個樣本 maven.xml 文件介紹 Jelly 腳本編制語言。

編寫 <preGoal> 和 <postGoal>
Ant 的 <target>makefile 規則的相似之處在於:定義了規則以後,前提條件和後置條件是固定的。這使得在多個項目間重用構建規則變得更加困難。例如,某個項目中的compile target 可能依靠 XDoclet 生成源文件,而另一個compile target 可能不包括任何先決條件。爲了克服這種限制,Maven 提供了兩個特殊標記:<preGoal><postGoal> 。從標記的名稱可以看出: preGoal 定義在指定的 goal 之前執行的構建規則。另一方面,postGoal 定義實現指定 goal 之後要執行的構建規則。例如,清單 7 中的preGoal 指示 Maven 在編譯源代碼之前用 XDoclet 生成源文件。


清單 7. 樣本 preGoal 部分

        <preGoal name="java:compile">
          <attainGoal name="xdoclet:ejbdoclet"/>
        </preGoal>

Maven 還提供與 Ant 的 <antcall> 標記相似的 <attainGoal> 標記,以便在確有必要直接實現 goal 的情況(如上例)下使用。

編寫自己的 goal
如果 goal 是特定於項目的,則可在 maven.xml 文件中定義自己的 goal。這些自定義的 goal 會覆蓋其它同名的 goal。如果項目包括子項目,子項目也繼承這些 goal。

編寫插件
爲了在項目間共享 goal,可在 Maven 安裝插件目錄( ${MAVEN_HOME}/plugins )中將其打包爲插件。典型的 Maven 插件包含 project.xml 和 plugin.jelly 文件。project.xml 文件描述插件的 POM;plugin.jelly 類似 maven.xml 且包含該插件所展示的 goal。插件可以有自己的資源和相關性信息。預先定義的變量${plugin.dir} 讓用戶引用插件目錄中的資源。例如,在清單 8 中所示的插件結構中, ${plugin.dir}/dtd/web-app_2_3.dtd 可訪問plugin.jelly 中的web-app_2_3.dtd


清單 8. 樣本插件結構

    ejbjar-plugin-1.0
    |-- dtd
    |   |-- application_1_3.dtd
    |   |-- ejb-jar_2_0.dtd
    |   |-- web-app_2_3.dtd
    |-- plugin.jelly
    `-- project.xml


安裝 Maven

最近發行的 Maven 1.0-beta-8 基本上是 1.0 的功能完善版。因爲 Maven 開發社區每天都在修正錯誤,如果您遇到任何無法正常工作的問題,則立即從 CVS(Concurrent Version System,併發版本控制系統)獲得最新 Maven 版本,然後自行構建(請參閱參考資料以獲得指示信息)。下載了最新的 Maven 源代碼之後,可通過調用以下命令來構建 Maven:

      ant -f build-bootstrap.xml
      (set MAVEN_HOME to where you want Maven to reside and
      use Ant 1.5.1 to perform the build)

如果在防火牆之後操作,請正確設置以下特性: maven.proxy.hostmaven.proxy.portmaven.proxy.usernamemaven.proxy.password 。缺省情況下,Maven 資源庫駐留在${MAVEN_HOME}/repository 中;通過將maven.repo.local 特性設置爲新位置,可以更改 Maven 資源庫的位置。

樣本項目文件

請參閱在這個樣本 J2EE 項目中使用的 Maven 項目文件。


掌握了到目前爲止所學的知識後,就可以着手使用 Maven 了。本節描述如何用 Maven 設置一個樣本 J2EE 項目。


清單 9. 樣本項目目錄佈局

在進行詳細介紹之前,我先說明一下項目的目錄佈局。儘管不作要求,但事實證明一致的跨項目目錄佈局非常有用,因爲熟悉了一個項目的開發人員可以輕鬆地瀏覽其它項目。更重要的是,一致的目錄佈局可讓您編寫通用的構建規則。

Maven 的目錄佈局指南(請參閱 參考資料)適用於絕大多數項目。作爲演示,我使用略微不同的佈局,如清單 9 所示。


清單 9. 樣本項目目錄佈局

 project
 |
 |-- LICENSE.txt
 |-- project.properties
 |-- maven.xml
 |-- project.xml
 |-- src
 |   `-- java
 |       `-- com/....
 |   `-- conf
 |       `-- Configuration files for staging environments.
 |-- test
 |   `-- java
 |       `-- com/....
 |   `-- conf
 |       `-- Configuration files for unit testing environments.
 `-- xdocs
     `-- index.xml

一個 J2EE 項目通常生成 WAR 文件、EJB JAR 文件和 EAR 文件。因爲每種文件都包括自己的相關性信息和源文件,所以應將其作爲單獨項目來構建。通常,通過將子項目存儲爲主項目的子目錄,來構造這一項目/子項目關係。我們的佈局如清單 10 所示。


清單 10. Maven 中 J2EE 項目的高級目錄佈局

j2ee-project
|
|-- project.xml       - Produces the EAR file
|
|-- util-subproject
|   |
|   `-- project.xml   - Produces the Utility JAR file
|
|-- ejb-subproject
|   |
|   `-- project.xml   - Produces the EJB JAR file
|
`-- web-subproject
    |
    `-- project.xml   - Produces the WAR file


項目繼承:

項目繼承讓 POM 以類似於對象繼承的方式從主 POM 繼承 ― 由於這些項目之間的細微差別(主要是相關性的差別),這一特性在此尤爲重要。項目管理部分可在主 project.xml 中集中維護。要使用項目繼承,可使用 project.xml 中的<extend> 標記(請參閱“ 樣本項目文件”中的清單 2)。


樣本項目的goal:

既然已經定義了 POM,就可以編寫它們的 goal。因爲這些 goal 使用 POM 中定義的特性,所以在繼續之前應該首先理解“ 樣本項目文件”中的 project.xml 文件。


Utility 子項目:

由於 Utility 子項目生成一個包含源目錄中類的 JAR 文件 ― 由缺省的 jar:jar goal 即可滿足要求,因此這裏不需要定製的 goal。

因爲 Web 子項目和 EJB 子項目都依靠 Utility 子項目,所以,在構建 Web 子項目和 EJB 子項目之前,應該調用 jar:install goal 以將 Utility 子項目 JAR 文件部署到本地資源庫。這樣,WAR 子項目和 EJB 子項目就可以正確地解析相關性。


Web 子項目:

Web 子項目生成一個 WAR 文件,該文件包含源目錄的類、 jsp 目錄中的 JSP 文件和 conf 目錄中的 web.xml 文件。缺省war:war goal 有更簡單的關於項目目錄佈局的視圖。要重用該 goal,可如下定製其行爲:

  1. 在項目的 project.properties 文件中,將特性 maven.war.srcmaven.war.webxml 分別設置爲${maven.build.dir}/webapp${maven.src.dir}/conf/web.xml 。這告訴war:war 在哪裏查找 Web 來源(JSP 頁面、HTML 靜態頁面和圖像等)和 web.xml 文件。
  2. 定義一個將所有 JSP 文件複製到 ${maven.build.dir}/webapp 目錄中的 preGoal 。以下 maven.xml 可實現這一效果:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <project>
      <preGoal name="war:init">
        <copy todir="${maven.build.dir}/webapp">
          <fileset dir="${maven.src.dir}/jsp" include="*.jsp"/>
        </copy>
      </preGoal>
    </project>
    

當調用 war:war goal 時,請注意 Utility JAR 文件和 commons-beanutils JAR 文件都被打包到 WAR 文件。通過查看 project.xml 文件的相關性部分中的war.bundle.jar 特性,Maven 知道要在 WAR 文件中包括哪個文件。

EJB子項目:

給 EJB JAR 文件打包和給 JAR 文件打包相似。如果項目設置與缺省 ejb goal 不匹配,可應用以上“Web 子項目”一節中所描述的技術。在這個特定例子中,將 ejb-jar.xml 從conf 目錄複製到${maven.build.dir}/ejb/META-INF 目錄,並將maven.ejb.src 特性設置爲${maven.build.dir}/ejb

要將相關性 JAR 文件添加到 EJB JAR 的清單類路徑(manifest classpath)中,可在相關性部分中使用 ejb.manifest.classpath 特性。


主(EAR)項目:

在成功編譯並部署了子項目(使用 jar:installwar:installejb:install goal)之後,即可創建最終的 EAR 文件。相關性特性ear.bundle.jarear.bundle.ejbear.bundle.war 告訴ear 插件要在 EAR 文件中包括哪些文件。(對於 Maven 1.0-beta-8,WAR 文件不是受支持的相關性類型,因此 EAR 插件不能正確地給 WAR 文件打包。解決辦法:使用postGoal 手工更新 EAR 文件。)

reactor:自動構建子項目:

構建 J2EE 項目需要大量的工作。事實證明,每次項目更改時重複同樣的過程耗費時間而且容易出錯。爲幫助解決這些問題,Maven 的 reactor 功能部件以正確的順序自動構建子項目,這樣就節省了時間且減少了錯誤。

清單 11 的 maven.xml 演示了定義 reactor 的方法。


清單 11. 樣本 reactor 定義

<?xml version="1.0" encoding="ISO-8859-1"?>
<project default="all"
         xmlns:m="jelly:maven">
  <goal name="all">
    <m:reactor basedir="${basedir}"
               includes="*/project.xml"
               goals="install"
               banner="Building"
               ignoreFailures="false"/>
  </goal>
</project>

該 reactor 首先在 basedir 目錄下搜索 project.xml 文件,然後調用 install goal。執行的順序取決於每個項目中的相關性部分。此外,通常可以在主項目的 maven.xml 文件中定義 reactor。因爲 goal 在子項目中繼承,所以選擇 goal 的名稱時要當心。

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