Maven 項目構建基礎

Maven

Maven 項目構建基礎

Maven 是功能強大的構建工具能夠幫我們自動化構建過程,從清理、編譯、測試到生成報告,再到打包和部署。我們只需要輸入簡單的命令(如 mvn clean install),Maven 就會幫我們處理繁瑣的任務;它最大化的消除了構建的重複,抽象了構建生命週期,並且為絕大部分的構建任務提供了已實現的插件。比如說測試,我們只需要遵循 Maven 的約定編寫好測試用例,當我們運行構建的時候,這些測試便會自動運行。除此之外,Maven 能幫助我們標準化構建過程。在 Maven 之前,十個項目可能有十種構建方式,但通過 Maven,所有項目的構建命令都是簡單一致的。有利於促進項目團隊的標準化。

構建工具對比

Maven 是筆者接觸的第一個脫離於 IDE 的命令行構建工具,筆者之前一直是基於 Visual Studio 下進行 Windows 驅動開發,並不是很能明白 Builder 與 IDE 之間的區別。依賴大量的手工操作。編譯、測試、代碼生成等工作都是相互獨立的,很難一鍵完成所有工作。手工勞動往往意味著低效,意味著容易出錯。很難在項目中統一所有的 IDE 配置,每個人都有自己的喜好。也正是由於這個原因,一個在機器 A 上可以成功運行的任務,到了機器 B 的 IDE 中可能就會失敗。

在 Linux C 開發中我們常常使用 Make 進行構建,不過 Make 將自己和操作系統綁定在一起了;也就是說,使用Make,就不能實現(至少很難)跨平臺的構建,這對於Java來說是非常不友好的。此外,Makefile 的語法也成問題,很多人抱怨 Make 構建失敗的原因往往是一個難以發現的空格或 Tab 使用錯誤。而在 Java 發展過程中常見的自動化構建工具以 Ant、Maven、Gradle 為代表,整個自動化流程往往包含以下步驟:編譯源代碼、運行單元測試和集成測試、執行靜態代碼分析、生成分析報告、創建發佈版本、部署到目標環境、部署傳遞過程以及執行冒煙測試和自動功能測試。

和 Make 一樣,Ant 也都是過程式的,開發者顯式地指定每一個目標,以及完成該目標所需要執行的任務。針對每一個項目,開發者都需要重新編寫這一過程,這裡其實隱含著很大的重複。Maven 是聲明式的,項目構建過程和過程各個階段所需的工作都由插件實現,並且大部分插件都是現成的,開發者只需要聲明項目的基本元素,Maven 就執行內置的、完整的構建過程。這在很大程度上消除了重複。

此外,Ant 是沒有依賴管理的,所以很長一段時間 Ant 用戶都不得不手工管理依賴,這是一個令人頭疼的問題。幸運的是,Ant 用戶現在可以藉助 Ivy 管理依賴。而對於 Maven 用戶來說,依賴管理是理所當然的,Maven 不僅內置了依賴管理,更有一個可能擁有全世界最多 Java 開源軟件包的中央倉庫,Maven 用戶無須進行任何配置就可以直接享用。

而 Gradle 拋棄了 Maven 的基於 XML 的繁瑣配置;眾所周知 XML 的閱讀體驗比較差,對於機器來說雖然容易識別,但畢竟是由人去維護的。取而代之的是 Gradle 採用了領域特定語言 Groovy 的配置,大大簡化了構建代碼的行數。Maven 的設計核心 Convention Over Configuration 被 Gradle 更加發揚光大,而 Gradle 的配置即代碼又超越了Maven。在 Gradle 中任何配置都可以作為代碼被執行的,我們也可以隨時使用已有的 Ant 腳本(Ant task 是 Gradle 中的一等公民)、Java 類庫、Groovy 類庫來輔助完成構建任務的編寫。在現代 Java 開發基礎系列文章中也有專門的章節講解 Gradle,筆者在 Android 與 Spring 項目構建中也會優先選擇 Gradle。

環境配置

Maven 的安裝也非常方便,可從 Apache 官方下載最新的 Maven 壓縮包然後解壓,也可以使用SDK Man 執行安裝;如果是手動配置的話我們還需要配置設置下系統的環境變量:

  • M2HOME: 指向Maven安裝目錄
  • Path: 追加 Maven 安裝目錄下的 bin 目錄

在用戶目錄下,我們可以發現 .m2 文件夾。默認情況下,該文件夾下放置了 Maven 本地倉庫 .m2/repository。所有的 Maven 構件(artifact)都被存儲到該倉庫中,以方便重用。默認情況下,~/.m2 目錄下除了 repository 倉庫之外就沒有其他目錄和文件了,不過大多數 Maven 用戶需要複製 M2HOME/conf/settings.xml 文件到 ~/.m2/settings.xml。

部分常用的Maven命令如下:

<code>mvn -v # 查看maven版本mvn compile # 編譯mvn test # 測試mvn package # 打包mvn clean # 刪除 targetmvn install # 安裝jar包到本地倉庫中mvn archetype:generate -DgroupId=co.hoteam -DartifactId=Zigbee -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false # 創建一個新工程/<code>

網絡代理

眾所周知的原因,國內有時候並不能夠很順暢的訪問 Maven 的中央倉庫,往往我們需要訪問國內的鏡像地址:

<code><mirror>CN<name>OSChina Central/<name> http://maven.oschina.net/content/groups/public/<mirrorof>central/<mirrorof>/<mirror>/<code>

或者編輯 ~/.m2/settings.xml 文件(如果沒有該文件,則複製 $M2HOME/conf/settings.xml),添加代理配置如下:

<code><settings>  ...<pqroxies>  <proxy>    my-proxy    <active>true/<active>    <protocol>http/<protocol>    <host>代理服務器主機名/<host>    <port>端口號/<port>      /<proxy>    .../<pqroxies>/<settings>/<code>

如果不行試試重啟機器或者eclipse等ide還不行試試下面這種方式:windows-->preferences-->maven-->installations add

Maven 項目構建基礎

這樣配置後將使用指定目錄下的maven,而非eclipse的maven內置插件。

其他錯誤處理

(1)有時候執行mvn compile時候會爆出無法找到junit的錯誤,可能的解決方法有:

  • 在Eclipse的Projects選項中使用Projects Clean
  • 在pom.xml中引入junit依賴項,並且保證其scope為compile:

<dependency> <groupid>junit/<groupid> <artifactid>junit/<artifactid> <version>4.11/<version> <scope>test/<scope> /<dependency>

(2)有時候在Eclipse下執行mvn compile或者相關命令時,會報某某文件出現不識別字符或者非UTF-8編碼,此時可以做幾步檢查:

  • 檢查對應的Java文件是否有Bom頭
  • 檢查對應的Java文件的編碼
  • 如果都沒有問題,在Eclipse中先將文件編碼設置為GBK,再改回UTF-8試試。

項目配置

就像 Make 的 Makefile,Ant 的 build.xml 一樣,Maven 項目的核心是 pom.xml。首先創建一個名為 hello-world 的文件夾,打開該文件夾,新建一個名為 pom.xml 的文件,輸入其內容如下:

<code><project><modelversion>4.0.0/<modelversion><groupid>com.wx.mvn/<groupid><artifactid>hello-world/<artifactid><version>1.0-SNAPSHOT/<version><name>Maven Hello World Project/<name>/<project>/<code>

文件結構

  • 代碼的第一行是 XML 頭,指定了該 xml 文檔的版本和編碼方式。緊接著是 project 元素,project 是所有 pom.xml 的根元素,它還聲明瞭一些 POM 相關的命名空間及 xsd 元素,雖然這些屬性不是必須的,但使用這些屬性能夠讓第三方工具(如 IDE 中的 XML 編輯器)幫助我們快速編輯 POM。
  • 根元素下的第一個子元素 modelVersion 指定了當前 POM 模型的版本,對於 Maven 2 及 Maven 3 來說,它只能是4.0.0。這段代碼中最重要的是 groupId,artifactId 和 version 三行。這三個元素定義了一個項目基本的座標,在Maven 的世界,任何的 jar、pom 或者 war 都是以基於這些基本的座標進行區分的。
  • groupId 定義了項目屬於哪個組,這個組往往和項目所在的組織或公司存在關聯,譬如你在 googlecode 上建立了一個名為 myapp 的項目,那麼 groupId 就應該是 com.googlecode.myapp,如果你的公司是 mycom,有一個項目為 myapp,那麼 groupId 就應該是 com.mycom.myapp。
  • artifactId 定義了當前 Maven 項目在組中唯一的 ID,我們為這個 Hello World 項目定義 artifactId 為 hello-world,本書其他章節代碼會被分配其他的 artifactId。而在前面的 groupId 為 com.googlecode.myapp 的例子中,你可能會為不同的子項目(模塊)分配 artifactId,如:myapp-util、myapp-domain、myapp-web 等等。
  • version 指定了 Hello World 項目當前的版本——1.0-SNAPSHOT。SNAPSHOT 意為快照,說明該項目還處於開發中,是不穩定的版本。隨著項目的發展,version 會不斷更新,如升級為 1.0、1.1-SNAPSHOT、1.1、2.0 等等。
  • 最後一個 name 元素聲明瞭一個對於用戶更為友好的項目名稱,雖然這不是必須的,但我還是推薦為每個 POM 聲明 name,以方便信息交流。 沒有任何實際的 Java 代碼,我們就能夠定義一個 Maven 項目的 POM,這體現了 Maven 的一大優點,它能讓項目對象模型最大程度地與實際代碼相獨立,我們可以稱之為解耦,或者正交性,這在很大程度上避免了 Java 代碼和 POM 代碼的相互影響。比如當項目需要升級版本時,只需要修改 POM,而不需要更改 Java 代碼;而在 POM 穩定之後,日常的 Java 代碼開發工作基本不涉及 POM 的修改。

變量替換

在 pom.xml 定義 properties 標籤

<code><properties> <project.build.sourceencoding>UTF-8/<project.build.sourceencoding> <spring.version>1.2.6/<spring.version> <developer.organization>/<properties>/<code>

以上內容就改成了

<code><dependency> <groupid>org.springframework/<groupid> <artifactid>spring-core/<artifactid> <version>${spring.version}/<version>/<dependency><dependency> <groupid>org.springframework/<groupid> <artifactid>spring-aop/<artifactid> <version>${spring.version}/<version>/<dependency>/<code>

也可以使用 maven-properties 插件來支持外部變量

目錄結構

項目主代碼和測試代碼不同,項目的主代碼會被打包到最終的構件中(比如 jar),而測試代碼只在運行測試時用到,不會被打包。默認情況下,Maven 假設項目主代碼位於 src/main/java 目錄,我們遵循 Maven 的約定,創建該目錄,然後在該目錄下創建文件 com/wx/mvn/helloworld/HelloWorld.java,其內容如下:

<code>package com.wx.mvn.helloworld;public class HelloWorld{public String sayHello()    {return "Hello Maven";    }public static void main(String[] args)    {        System.out.print( new HelloWorld().sayHello() );    }}/<code>

關於該 Java 代碼有兩點需要注意。首先,大部分情況下我們應該把項目主代碼放到 src/main/java/ 目錄下(遵循Maven的約定),而無須額外的配置,Maven 會自動搜尋該目錄找到項目主代碼。其次,該 Java 類的包名是 com.wx.mvn.helloworld,這與我們之前在 POM 中定義的 groupId 和 artifactId 相吻合。一般來說,項目中 Java 類的包都應該基於項目的 groupId 和 artifactId,這樣更加清晰,更加符合邏輯,也方便搜索構件或者 Java 類。 代碼編寫完畢後,我們使用 Maven 進行編譯,在項目根目錄下運行命令 mvn clean compile 即可。Maven 首先執行了clean:clean 任務,刪除 target/ 目錄,默認情況下 Maven 構建的所有輸出都在 target/ 目錄中;接著執行 resources:resources 任務(未定義項目資源,暫且略過);最後執行 compiler:compile 任務,將項目主代碼編譯至 target/classes 目錄(編譯好的類為 com/wx/mvn/helloworld/HelloWorld.Class)。

倉庫配置

Maven 項目構建基礎

下面介紹一些 Maven 倉庫工作的原理。典型的一個 Maven依賴下會有這三個文件:

<code>maven-metadata.xmlmaven-metadata.xml.md5maven-metadata.xml.sha1/<code>

maven-metadata.xml裡面記錄了最後deploy的版本和時間。

<code><metadata>  <groupid>io.github.hengyunabc/<groupid>  <artifactid>mybatis-ehcache-spring/<artifactid>  <version>0.0.1-SNAPSHOT/<version>  <versioning>    <snapshot>      <timestamp>20150804.095005/<timestamp>      <buildnumber>1/<buildnumber>    /<snapshot>    <lastupdated>20150804095005/<lastupdated>  /<versioning>/<metadata>/<code>

其中 md5, sha1 校驗文件是用來保證這個 meta 文件的完整性。Maven 在編繹項目時,會先嚐試請求 maven-metadata.xml,如果沒有找到,則會直接嘗試請求到jar文件,在下載 jar 文件時也會嘗試下載 jar 的 md5, sha1 文件。Maven 的 repository 並沒有優先級的配置,也不能單獨為某些依賴配置 repository。所以如果項目配置了多個repository,在首次編繹時,會依次嘗試下載依賴。如果沒有找到,嘗試下一個,整個流程會很長。所以儘量多個依賴放同一個倉庫,不要每個項目都有一個自己的倉庫。如果想要使用本地file倉庫裡,在項目的pom.xml裡配置,如:

<code><repositories>       <repository>           hengyunabc-maven-repo           file:/home/hengyunabc/code/maven-repo/repository/       /<repository>/<repositories>/<code>


分享到:


相關文章: