《Maven實戰》整理

《Maven實戰》整理

1 Maven 介紹

  • Maven這個詞可以翻譯爲“知識的積累”,也可以翻譯爲“專家”或“內行”。本書將介紹Maven這一跨平臺的項目管理工具。作爲Apache組織中的一個頗爲成功的開源項目,Maven主要服務於基於Java平臺的項目構建、依賴管理和項目信息管理。無論是小型的開源類庫項目,還是大型的企業級應用;無論是傳統的瀑布式開發,還是流行的敏捷模式,Maven都能大顯身手。1
  • Maven是優秀的構建工具,能夠幫我們自動化構建過程,從清理、編譯、測試到生成報告,再到打包和部署。
  • Maven能幫助我們標準化構建過程。在Maven之前,十個項目可能有十種構建方式;有了Maven之後,所有項目的構建命令都是簡單一致的,這極大地避免了不必要的學習成本,而且有利於促進項目團隊的標準化。
  • Maven不僅是構建工具,還是一個依賴管理工具和項目信息管理工具。它提供了中央倉庫,能幫我們自動下載構件。 幾乎任何Java應用都會借用一些第三方的開源類庫,這些類庫都可通過依賴的方式引入到項目中來。隨着依賴的增多,版本不一致、版本衝突、依賴臃腫等問題都會接踵而來。手工解決這些問題是十分枯燥的,幸運的是Maven提供了一個優秀的解決方案,它通過一個座標系統準確地定位每一個構件(artifact),也就是通過一組座標Maven能夠找到任何一個Java

類庫(如jar文件)。Maven爲全世界的Java開發者提供了一個免費的中央倉庫,在其中幾乎可以找到任何的流行開源類庫。

2 Maven與其他構建工具的比較(IDE,Make,Ant)

 

2.1 形象的比喻

使用腳本建立高度自定義的構建系統就像買組裝PC,耗時費力,結果也不一定很好。當然,你可以享受從無到有的樂趣,但恐怕實際項目中無法給你那麼多時間。使用Maven就像購買品牌PC,省時省力,並能得到成熟的構建系統,還能得到來自於Maven社區的大量支持。

2.2 IDE

依賴大量的手工操作。編譯、測試、代碼生成等工作都是相互獨立的,很難一鍵完成所有工作。手工勞動往往意味着低效,意味着容易出錯。很難在項目中統一所有的IDE配置,每個人都有自己的喜好。也正是由於這個原因,一個在機器A上可以成功運行的任務,到了機器B的IDE中可能就會失敗。

2.3 Make

Make將自己和操作系統綁定在一起了。也就是說,使用Make,就不能實現(至少很難)跨平臺的構建,這對於Java來說是非常不友好的。此外,Makefile的語法也成問題,很多人抱怨Make構建失敗的原因往往是一個難以發現的空格或Tab使用錯誤。

2.4 Ant

  • 和Make一樣,Ant也都是過程式的,開發者顯式地指定每一個目標,以及完成該目標所需要執行的任務。針對每一個項目,開發者都需要重新編寫這一過程,這裏其實隱含着很大的重複。Maven是聲明式的,項目構建過程和過程各個階段所需的工作都由插件實現,並且大部分插件都是現成的,開發者只需要聲明項目的基本元素,Maven就執行內置的、完整的構建過程。這在很大程度上消除了重複。
  • Ant是沒有依賴管理的,所以很長一段時間Ant用戶都不得不手工管理依賴,這是一個令人頭疼的問題。幸運的是,Ant用戶現在可以藉助Ivy管理依賴。而對於Maven用戶來說,依賴管理是理所當然的,Maven不僅內置了依賴管理,更有一個可能擁有全世界最多Java開源軟件包的中央倉庫,Maven用戶無須進行任何配置就可以直接享用。

3 Maven的安裝和配置

 

3.1 maven 安裝

這部分相對基礎,就簡要帶過了。可從apache官方下載最新的Maven 壓縮包,解壓即可。然後設置下系統的環境變量。如下所示:

  • M2HOME:maven安裝目錄
  • Path:追繳maven安裝目錄下的bin目錄

3.2 maven 安裝目錄介紹

  • bin

該目錄包含了mvn運行的腳本,這些腳本用來配置Java命令,準備好classpath和相關的Java系統屬性,然後執行Java命令。其中mvn是基於UNIX平臺的shell腳本,mvn.bat是基於Windows平臺的bat腳本。

  • boot

該目錄只包含一個文件,以maven 3.0爲例,該文件爲plexus-classworlds-2.2.3.jar。plexus-classworlds是一個類加載器框架,相對於默認的java類加載器,它提供了更豐富的語法以方便配置,Maven使用該框架加載自己的類庫。更多關於classworlds的信息請參考http://classworlds.codehaus.org/。 對於一般的Maven用戶來說,不必關心該文件。

  • conf

該目錄包含了一個非常重要的文件settings.xml。直接修改該文件,就能在機器上全局地定製Maven的行爲。

  • lib

該目錄包含了所有Maven運行時需要的Java類庫,Maven本身是分模塊開發的,因此用戶能看到諸如mavn-core-3.0.jar、maven-model-3.0.jar之類的文件,此外這裏還包含一些Maven用到的第三方依賴如common-cli-1.2.jar、google-collection-1.0.jar等等。

3.3 ~/.m2目錄

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

3.4 設置HTTP代理

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

<settings>
  ...
<pqroxies>
  <proxy>
    <id>my-proxy</id>
    <active>true</active>
    <protocol>http</protocol>
    <host>代理服務器主機名</host>
    <port>端口號</port>
    <!--
        <username>***</username>
        <password>***</password>
        <nonProxyHosts>repository.mycom.com|*.google.com</nonProxyHosts>
-->
  </proxy>
  </proxies>
  ...
</settings>

3.5 m2eclipse(eclipse maven插件)

啓動Eclipse之後,在菜單欄中選擇Help,然後選擇 Install New Software…,接着你會看到一個Install對話框,點擊Work with:字段邊上的Add按鈕,你會得到一個新的Add Repository對話框,在Name字段中輸入m2e,Location字段中輸入http://m2eclipse.sonatype.org/sites/m2e ,然後點擊OK。Eclipse會下載m2eclipse安裝站點上的資源信息。

4 Maven使用

 

4.1 編寫POM

就像Make的Makefile,Ant的build.xml一樣,Maven項目的核心是pom.xml。

首先創建一個名爲hello-world的文件夾(本書中各章的代碼都會對應一個以ch開頭的項目),打開該文件夾,新建一個名爲pom.xml的文件,輸入其內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>com.juvenxu.mvnbook</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Maven Hello World Project</name>
</project>
  • 代碼的第一行是XML頭,指定了該xml文檔的版本和編碼方式。緊接着是project元素,project是所有pom.xml的根元素,它還聲明瞭一些POM相關的命名空間及xsd元素,雖然這些屬性不是必須的,但使用這些屬性能夠讓第三方工具(如IDE中的XML編輯器)幫助我們快速編輯POM。
  • 根元素下的第一個子元素modelVersion指定了當前POM模型的版本,對於Maven2及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。本書中所有的代碼都基於groupId com.juvenxu.mvnbook。
  • 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等等。本書的6.5小節會詳細介紹SNAPSHOT,第13章介紹如何使用Maven管理項目版本的升級發佈。

最後一個name元素聲明瞭一個對於用戶更爲友好的項目名稱,雖然這不是必須的,但我還是推薦爲每個POM聲明name,以方便信息交流。 沒有任何實際的Java代碼,我們就能夠定義一個Maven項目的POM,這體現了Maven的一大優點,它能讓項目對象模型最大程度地與實際代碼相獨立,我們可以稱之爲解耦,或者正交性,這在很大程度上避免了Java代碼和POM代碼的相互影響。比如當項目需要升級版本時,只需要修改POM,而不需要更改Java代碼;而在POM穩定之後,日常的Java代碼開發工作基本不涉及POM的修改。

4.2 編寫主代碼

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

package com.juvenxu.mvnbook.helloworld;

public class HelloWorld
{
    public String sayHello()
    {
        return "Hello Maven";
    }

    public static void main(String[] args)
    {
        System.out.print( new HelloWorld().sayHello() );
    }
}

關於該Java代碼有兩點需要注意。首先,在95%以上的情況下,我們應該把項目主代碼放到src/main/java/目錄下(遵循Maven的約定),而無須額外的配置,Maven會自動搜尋該目錄找到項目主代碼。其次,該Java類的包名是com.juvenxu.mvnbook.helloworld,這與我們之前在POM中定義的groupId和artifactId相吻合。一般來說,項目中Java類的包都應該基於項目的groupId和artifactId,這樣更加清晰,更加符合邏輯,也方便搜索構件或者Java類。 代碼編寫完畢後,我們使用Maven進行編譯,在項目根目錄下運行命令 mvn clean compile 即可。

clean告訴Maven清理輸出目錄target/,compile告訴Maven編譯項目主代碼,從輸出中我們看到Maven首先執行了clean:clean任務,刪除target/目錄,默認情況下Maven構建的所有輸出都在target/目錄中;接着執行resources:resources任務(未定義項目資源,暫且略過);最後執行compiler:compile任務,將項目主代碼編譯至target/classes目錄(編譯好的類爲com/juvenxu/mvnbook/helloworld/HelloWorld.Class)。

4.3 編寫測試代碼

爲了使項目結構保持清晰,主代碼與測試代碼應該分別位於獨立的目錄中。3.2節講過Maven項目中默認的主代碼目錄是src/main/java,對應地,Maven項目中默認的測試代碼目錄是src/test/java。因此,在編寫測試用例之前,我們先創建該目錄。 在Java世界中,由Kent Beck和Erich Gamma建立的JUnit是事實上的單元測試標準。要使用JUnit,我們首先需要爲Hello World項目添加一個JUnit依賴,修改項目的POM如代碼清單。

<?xml version="1.0" encoding="UTF-8"?>
<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>com.juvenxu.mvnbook</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Maven Hello World Project</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

代碼中添加了dependencies元素,該元素下可以包含多個dependency元素以聲明項目的依賴,這裏我們添加了一個依賴——groupId是junit,artifactId是junit,version是4.7。前面我們提到groupId、artifactId和version是任何一個Maven項目最基本的座標,JUnit也不例外,有了這段聲明,Maven就能夠自動下載junit-4.7.jar。也許你會問,Maven從哪裏下載這個jar呢?在Maven之前,我們可以去JUnit的官網下載分發包。而現在有了Maven,它會自動訪問中央倉庫(http://repo1.maven.org/maven2/) ,下載需要的文件。讀者也可以自己訪問該倉庫,打開路徑junit/junit/4.7/,就能看到junit-4.7.pom和junit-4.7.jar。

上述POM代碼中還有一個值爲test的元素scope,scope爲依賴範圍,若依賴範圍爲test則表示該依賴只對測試有效,換句話說,測試代碼中的import JUnit代碼是沒有問題的,但是如果我們在主代碼中用import JUnit代碼,就會造成編譯錯誤。如果不聲明依賴範圍,那麼默認值就是compile,表示該依賴對主代碼和測試代碼都有效。

配置了測試依賴,接着就可以編寫測試類,回顧一下前面的HelloWorld類,現在我們要測試該類的sayHello()方法,檢查其返回值是否爲“Hello Maven”。在src/test/java目錄下創建文件,其內容如代碼清單如下:

package com.juvenxu.mvnbook.helloworld;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class HelloWorldTest
{
    @Test
    public void testSayHello()
    {
        HelloWorld helloWorld = new HelloWorld();

        String result = helloWorld.sayHello();

        assertEquals( "Hello Maven", result );
    }
}

測試用例編寫完畢之後就可以調用Maven執行測試,運行 mvn clean test。

構建在執行compiler:testCompile任務的時候失敗了,Maven輸出提示我們需要使用-source 5或更高版本以啓動註釋,也就是代碼中JUnit 4的@Test註解。這是Maven初學者常常會遇到的一個問題。由於歷史原因,Maven的核心插件之一compiler插件默認只支持編譯Java 1.3,因此我們需要配置該插件使其支持Java 5,見代碼清單:

<project>
…
<build>
<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>
</build>
…
</project>

該POM省略了除插件配置以外的其他部分。現在再執行mvn clean test,結果正常。

4.4 打包和運行

將項目進行編譯、測試之後,下一個重要步驟就是打包(package)。Hello World的POM中沒有指定打包類型,使用默認打包類型jar,我們可以簡單地執行命令 mvn clean package 進行打包。
類似地,Maven會在打包之前執行編譯、測試等操作。這裏我們看到jar:jar任務負責打包,實際上就是jar插件的jar目標將項目主代碼打包成一個名爲hello-world-1.0-SNAPSHOT.jar的文件,該文件也位於target/輸出目錄中,它是根據artifact-version.jar規則進行命名的,如有需要,我們還可以使用finalName來自定義該文件的名稱。

至此,我們得到了項目的輸出,如果有需要的話,就可以複製這個jar文件到其他項目的Classpath中從而使用HelloWorld類。但是,如何才能讓其他的Maven項目直接引用這個jar呢?我們還需要一個安裝的步驟,執行 mvn clean install:

在打包之後,我們又執行了安裝任務install:install,從輸出我們看到該任務將項目輸出的jar安裝到了Maven本地倉庫中,我們可以打開相應的文件夾看到Hello World項目的pom和jar。之前講述JUnit的POM及jar的下載的時候,我們說只有構件被下載到本地倉庫後,才能由所有Maven項目使用,這裏是同樣的道理,只有將Hello World的構件安裝到本地倉庫之後,其他Maven項目才能使用它。

到目前爲止,我們還沒有運行Hello World項目,因爲HelloWorld類有一個main方法的。默認打包生成的jar是不能夠直接運行的,因爲帶有main方法的類信息不會添加到manifest中(我們可以打開jar文件中的META-INF/MANIFEST.MF文件,將無法看到Main-Class一行)。爲了生成可執行的jar文件,我們需要藉助maven-shade-plugin,配置該插件如下:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.juvenxu.mvnbook.helloworld.HelloWorld</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

plugin元素在POM中的相對位置應該在<project><build><plugins>下面。我們配置了mainClass爲com.juvenxu.mvnbook.helloworld.HelloWorld,項目在打包時會將該信息放到MANIFEST中。現在執行 mvn clean install ,待構建完成之後打開target/目錄,我們可以看到hello-world-1.0-SNAPSHOT.jar和original-hello-world-1.0-SNAPSHOT.jar,前者是帶有Main-Class信息的可運行jar,後者是原始的jar

4.5 使用ArcheType生成項目骨架

Hello World項目中有一些Maven的約定:在項目的根目錄中放置pom.xml,在src/main/java目錄中放置項目 的主代碼,在src/test/java中放置項目的測試代碼。我之所以一步一步地展示這些步驟,是爲了能讓可能是Maven初學者的你得到最實際的感受。我們稱這些基本的目錄結構和pom.xml文件內容稱爲項目的骨架,當你第一次創建項目骨架的時候,你還會饒有興趣地去體會這些默認約定背後的思想,第二次,第三次,你也許還會滿意自己的熟練程度,但第四、第五次做同樣的事情,就會讓程序員惱火了,爲此Maven提供了Archetype以幫助我們快速勾勒出項目骨架。 還是以Hello World爲例,我們使用maven archetype來創建該項目的骨架,離開當前的Maven項目目錄。 如果是Maven 3,簡單的運行: mvn archetype:generate 如果是Maven 2,最好運行如下命令: mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-5:generate 很多資料會讓你直接使用更爲簡單的 mvn archetype:generate 命令,但在Maven2中這是不安全的,因爲該命令沒有指定archetype插件的版本,於是Maven會自動去下載最新的版本,進而可能得到不穩定的SNAPSHOT版本,導致運行失敗。然而在Maven 3中,即使用戶沒有指定版本,Maven也只會解析最新的穩定版本,因此這是安全的,具體內容見7.7小節。 我們實際上是在運行插件maven-archetype-plugin,注意冒號的分隔,其格式爲 groupId:artifactId:version:goal ,org.apache.maven.plugins 是maven官方插件的groupId,maven-archetype-plugin 是archetype插件的artifactId,2.0-alpha-5 是目前該插件最新的穩定版,generate是我們要使用的插件目標。 緊接着我們會看到一段長長的輸出,有很多可用的archetype供我們選擇,包括著名的Appfuse項目的archetype,JPA項目的archetype等等。每一個archetype前面都會對應有一個編號,同時命令行會提示一個默認的編號,其對應的archetype爲maven-archetype-quickstart,我們直接回車以選擇該archetype,緊接着Maven會提示我們輸入要創建項目的groupId、artifactId、 version、以及包名package,如下輸入並確認

Define value for groupId: : com.juvenxu.mvnbook
Define value for artifactId: : hello-world
Define value for version: 1.0-SNAPSHOT: :
Define value for package: com.juvenxu.mvnbook: : com.juvenxu.mvnbook.helloworld
Confirm properties configuration:
groupId: com.juvenxu.mvnbook
artifactId: hello-world
version: 1.0-SNAPSHOT
package: com.juvenxu.mvnbook.helloworld
Y: : Y

Archetype插件將根據我們提供的信息創建項目骨架。在當前目錄下,Archetype插件會創建一個名爲 hello-world(我們定義的artifactId)的子目錄,從中可以看到項目的基本結構:基本的pom.xml已經被創建,裏面包含了必要的信息以及一個junit依賴;主代碼目錄src/main/java已經被創建,在該目錄下還有一個Java類com.juvenxu.mvnbook.helloworld.App,注意這裏使用到了我們剛纔定義的包名,而這個類也僅僅只有一個簡單的輸出Hello World!的main方法;測試代碼目錄src/test/java也被創建好了,並且包含了一個測試用例com.juvenxu.mvnbook.helloworld.AppTest。

4.6 配合IDE使用

介紹前面Hello World項目的時候,我們並沒有涉及IDE,如此簡單的一個項目,使用最簡單的編輯器也能很快完成,但對於稍微大一些的項目來說,沒有IDE就是不可想象的。
可以使用的IDE很多.如Emacs,Eclipse,NetBeans 等等
這部分可以直接參考其他說明文檔,其中m2eclipse簡單使用也可以參考《Maven實戰》

Footnotes:

1 :《maven實戰》

Date: 2012-05-18 15:48:05

Author: csophys

Org version 7.8.09 with Emacs version 23

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