還記得圖12-1中的構建週期嗎?Maven的構建週期跟那個類似,你馬上就要經歷構建週期中的每個階段了。儘管本書中的源碼不是一個應用程序,我們還是會把它們統一放到一個叫做java7developer項目中。
這一節的重點是:
- 探索Maven POM文件(即構建腳本)的基礎;
- 如何編譯、測試和打包代碼(包括Scala和Groovy);
- 如何用環境配置處理多個環境;
- 如何生成一個包含各種報告的項目網站。
首先你要搞明白定義java7developer項目的pom.xml文件。
12.3.1 POM
java7developer項目用pom.xml表示,包括各種插件、資源,以及構建所需的其他元素。可以在解壓或簽出本書項目代碼的根目錄(從現在開始我們用$BOOK_CODE指代這個位置)中找到這個pom.xml文件。POM主要由四部分組成:
- 項目基本信息;
- 構建配置;
- 依賴項管理;
- 環境配置。
這是個相當長的文件,但實際上它沒有看起來那麼複雜。如果你想瞭解POM中可以包含哪些內容的完整細節,請參見Maven網站上的POM Reference(http://maven.apache.org/pom.html)。
接下來我們就要解釋java7developer項目pom.xml文件的這四部分,先從項目基本信息開始。
1. 項目基本信息
pom.xml文件中可以放入一系列的基本項目信息。代碼清單12-1列出的是最起碼的起步信息。
代碼清單12-1 項目基本信息
<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.java7developer</groupId>
<artifactId>java7developer</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version> //❶唯一標識
<name>java7developer</name>
<description>
Project source code for the book!
</description> //❷項目信息
<url>http://www.java7developer.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> //❸平台無關的字符編碼
</properties>
...
這個工件在Maven資源庫中的唯一標識符由三部分組成:第一部分是<groupId>
的值com.java7developer
❶;第二部分是<artifactId>
的值java7developer
。<packaging>
的值jar
告訴Maven你要構建一個JAR文件(這裡可能出現的值有war
、ear
、rar
、sar
和har
)。唯一標識的最後一部分是<version>
的值1.0.0
1,表明版本號(執行Maven發佈時會在這個值後面加上SNAPSHOT
)。
1 版本號的格式遵循Major.Minor.Trivial風格,這是我們的最愛!
文件中還指定了<projectName>
和<url>
,以及一些其他可選的項目信息❷。<sourceEncoding>
為UTF-8,這樣可以確保在所有平台上的構建都是一致的❸。
總的來說,這個配置會指導Maven構建出java7developer-1.0.0.jar工件,並把它存在Maven資源庫中的com/java7developer/1.0.0目錄下。
Maven版本和快照
作為Maven慣例優先原則的一部分,它傾向於以主要.次要.瑣碎的格式來設置版本號,並依照慣例在版本號後面加上
-SNAPSHOT
表示這是一個臨時性的工件。比如說,在你的團隊為即將發佈的1.0.0版本持續構建JAR時,Maven會依照慣例將版本號設置為1.0.0-SNAPSHOT
。這樣,各種Maven插件就知道這還不是生產版本,從而可以正確處理它。在把這個工件發佈到生產環境中時,要發佈為1.0.0,下一個修訂bug的版本要從1.0.1-SNAPSHOT
開始。Maven通過它的發佈插件把這些都自動化了。要瞭解更多細節,請參見發佈插件頁面(http://maven.apache.org/plugins/maven-release-plugin/)。現在你已經明白項目基本信息部分是什麼樣的了,接下來我們來看看
<build>
吧。
2. 構建配置
<build>
中包含執行Maven構建週期目標所需的插件2及相應的配置。在大多數項目中,這部分內容都相當少,因為通常用默認插件的默認設置就夠了。
2 如果你需要對構建進行配置,可以訪問Maven的插件頁面查看插件的完整列表(http://maven.apache.org/plugins/index.html)。
在java7developer項目中,<build>
中有幾個覆蓋了默認值的插件,以便可以:
- 構建Java 7代碼;
- 構建Scala和Groovy代碼;
- 運行Java、Scala和Groovy測試;
- 提供Checkstyle和FindBugs代碼指標報告。
插件是以JAR為主的工件(主要是用Java寫的)。要配置構建插件,需要把它放在pom.xml文件的<build><plugins>
中。跟所有Maven工件一樣,每個插件都有唯一標識,所以需要指定<groupId>
、<artifactId>
和<version>
信息。對插件的所有配置都放在<configuration>
中,並且每個插件的具體配置元素是不同的。比如編譯插件的配置元素有<source>
、<target>
和<showWarnings>
,這是編譯器獨有的配置信息。
代碼清單12-2列出的是java7developer項目的構建配置部分(完整的代碼清單及相應的解釋在附錄E中)。
代碼清單12-2 POM:構建信息
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version> //❶所用插件
<configuration>
<source>1.7</source>
<target>1.7</target> //❷編譯Java 7代碼
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings> //❸編譯器警告
<fork>true</fork>
<executable>${jdk.javac.fullpath}</executable> //❹javac的路徑
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.9</version>
<configuration>
<excludes> ﹃❺排除的測試
<exclude>
com/java7developer/chapter11/listing_11_2/TicketRevenueTest.java
</exclude>
<exclude>
com/java7developer/chapter11/listing_11_7/TicketTest.java
</exclude>
...
</excludes> ﹄❺排除的測試
</configuration>
</plugin>
</plugins>
</build>
因為Maven 3默認是編譯Java 1.5的代碼,而我們要編譯Java 1.7❷,所以需要指明編譯器插件(的版本)❶。
既然你打破了慣例,所以還要加上幾個編譯警告選項❸。接下來要指定Java 7安裝在哪兒❹ 。只需要把sample__build.properties文件另存為build.properties,並編輯其中的jdk.javac.fullpath
屬性,因為Maven會用到它。
Surefire插件是測試用的。在配置中我們排除了幾個失敗測試❺(有兩個第11章的TDD測試)。
現在構建部分已經講完了,可以進入POM中非常重要的部分了:依賴管理。
3. 依賴管理
大多數Java項目的依賴項列表都很長,java7developer項目也不例外。Maven Central Repository中有各種各樣的第三方類庫,所以Maven可以幫你管理這些依賴項。最重要的是,這些第三方類庫都有它們自己的pom.xml文件,會聲明各自的依賴項,Maven可以據此找出任何需要下載的其他類庫。
這些依賴項一開始主要分為兩個作用域(compile
和test
)3。設置作用域跟把JAR文件放到CLASSPATH下是一樣的效果。代碼清單12-3是java7developer項目的<dependencies>
部分。完整的代碼清單及相應的解釋在附錄E中。
3 J2EE/JEE項目通常也會用到runtime
作用域的依賴項。
代碼清單12-3 POM:依賴項
<dependencies>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version> //1工件的唯一ID
<scope>compile</scope> //2編譯作用域
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope> //3測試作用域
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>
...
</dependencies>
為了讓Maven找到你引用的工件,需要給它正確的<groupId>
、<artifactId>
和<version>
1。我們在之前提到過,把<scope>
設置為compile
2會把這些JAR加到CLASSPATH中用於代碼的編譯。將<scope>
設置為test
3會在Maven編譯和運行測試時把這些JAR加到CLASSPATH中。
但你怎麼知道該指定什麼<groupId>
、<artifactId>
和<version>
?答案是搜索Maven Central Repository(http://search.maven.org/),你幾乎總能找到答案。
如果找不到合適的工件,可以用install:install-file
目標自己手工下載和安裝插件。這裡有個安裝asm-4.0_RC1.jar類庫的例子。
mvn install:install-file
-Dfile=asm-4.0_RC1.jar
-DgroupId=org.ow2.asm
-DartifactId=asm
-Dversion=4.0_RC1
-Dpackaging=jar
這個命令運行完後,你應該能在本地資源庫的$HOME/.m2/repository/org/ow2/asm/asm/4.0_RC1/中找到安裝好的工件,就像Maven下載的一樣。
工件管理器 在你手工安裝一個第三方類庫時,你只是為自己裝的,團隊裡的其他人呢?在你做要跟同事共享的工件時也面臨相同的問題,但你也不能把它放到Maven Central中(因為那是你們的私有代碼)。 用二進制工件管理器可以解決這個問題,比如Nexus(http://nexus.sonatype.org/)。工件管理器就像你和團隊自有的本地Maven Central,外界無法訪問。大多數工件管理器還會緩存Maven Central和其他資源庫,你的開發團隊所需的依賴項都可以從它那裡得到。
環境配置是要搞懂的最後一部分POM了,它可以有效處理不同環境下的構建。
4. 環境配置
環境配置是Maven用來處理環境化(比如UAT跟生產環境之間的構建差異)或其他與普通構建稍有不同的構建變體的。java7developer項目中有個例子,其中一個環境配置會關閉編譯器和作廢警告,如代碼清單12-4所示。
代碼清單12-4 POM:環境配置
<profiles>
<profile>
<id>ignore-compiler-warnings</id> //❶該環境配置的ID
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<showDeprecation>false</showDeprecation>
<showWarnings>false</showWarnings> //❷關閉警告
<fork>true</fork>
<executable>${jdk.javac.fullpath}</executable>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
在執行Maven時用-P <id>
可以指定要啟用的環境配置(比如mvn compile -P ignore-compile-warnings
)❶。在這個環境配置被激活後,會使用指定的編譯器插件,作廢警告和其他編譯器警告都會被關閉❷。
在Introduction to Build Profiles(構建環境配置介紹)頁面可以找到更多關於環境配置和為其他環境化目的如何使用它們的信息(http://maven.apache.org/guides/introduction/introduction-to-profiles.html2)。
2 短鏈接:http://t.cn/zl7MyO1。——譯者注
終於完成了java7developer項目的pom.xml文件之旅,你是不是已經迫不及待地想構建它了?
12.3.2 運行示例
希望你已經把代碼下載下來了。你會在其中看到一些pom.xml文件,就是它們控制著Maven構建。
你會在這一節中經歷最常用的Maven構建週期目標(clean
、compile
、test
和install
)。第一個目標就是清除上次構建遺留的所有工件。
1. 清除
目標clean
會刪掉target目錄。請換到$BOOK_CODE目錄並執行clean
目標。
cd $BOOK_CODE
mvn clean
與你要用到的其他Maven構建目標不同,clean
不會自動調用。如果想清除上次構建的工件,必需手動加上clean
目標。
上次構建遺留的殘餘物已經清除了,接下來一般是執行編譯源碼的構建目標。
2. 編譯
目標compile
用pom.xml文件中的編譯器插件配置編譯在src/main/java、src/main/scala和src/main/groovy下的源碼。也就是說它會帶著加到CLASSPATH
中的編譯作用域依賴項執行Java、Scala和Groovy編譯器(javac
、scalac
和groovyc
)。Maven還會處理在src/main/resources目錄下的資源文件,確保它們作為編譯CLASSPATH
的一部分。
編譯後的類最終會出現在target/classes目錄下。請執行下面的Maven目標:
mvn compile
compile
目標的執行應該相當快,並且在控制器中應該有類似下面這種輸出。
...
[INFO] [properties:read-project-properties {execution: default}]
[INFO] [groovy:generateStubs {execution: default}]
[INFO] Generated 22 Java stubs
[INFO] [resources:resources {execution: default-resources}]
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Compiling 119 source files to C:\Projects\workspace3.6\code\trunk\target\classes
[INFO] [scala:compile {execution: default}]
[INFO] Checking for multiple versions of scala
[INFO] includes = [**/*.scala,**/*.java,]
[INFO] excludes =
[INFO] C:\Projects\workspace3.6\code\trunk\src\main\java:-1: info: compiling
[INFO] C:\Projects\workspace3.6\code\trunk\target\generated-sources\groovystubs\main:-1: info: compiling
[INFO] C:\Projects\workspace3.6\code\trunk\src\main\groovy:-1: info:
compiling
[INFO] C:\Projects\workspace3.6\code\trunk\src\main\scala:-1: info: compiling
[INFO] Compiling 143 source files to C:\Projects\workspace3.6\code\trunk\target\classes at 1312716331031
[INFO] prepare-compile in 0 s
[INFO] compile in 12 s
[INFO] [groovy:compile {execution: default}]
[INFO] Compiled 26 Groovy classes
[INFO]------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] -----------------------------------------------------------------
[INFO] Total time: 43 seconds
[INFO] Finished at: Sun Aug 07 12:25:44 BST 2011
[INFO] Final Memory: 33M/79M
[INFO] -----------------------------------------------------------------
在這一階段,src/test/java、src/test/scala和src/test/groovy目錄下的測試類還沒編譯。儘管專門有一個test-compile
目標編譯這些類,但最常見的方式是運行test
目標。
3. 測試
在目標test
中能見到Maven構建週期的實際效果。在你要求Maven運行測試目標時,它知道為了保證test
目標成功運行,需要把前面的所有構建週期目標都執行一下(包括compile
、test-compile
和一系列其他目標)。
Maven會通過神火(Surefire)插件,使用pom.xml文件中的測試提供者(作為測試作用域的依賴項,此例中為JUnit)運行測試。Maven不僅會運行測試,還會產生報告文件,測試完成後你可以分析這些報告,以便對失敗測試展開調研,並收集測試指標。
請執行下面的Maven命令:
mvn clean test
一旦完成測試類的編譯和運行,應該就能見到下面這種輸出。
...
Running com.java7developer.chapter11.listing_11_3.TicketRevenueTest
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec
Running com.java7developer.chapter11.listing_11_4.TicketRevenueTest
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec
Running com.java7developer.chapter11.listing_11_5.TicketTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.015 sec
Results :
Tests run: 20, Failures: 0, Errors: 0, Skipped: 0
[INFO]------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO]------------------------------------------------------------------
[INFO] Total time: 16 seconds
[INFO] Finished at: Wed Jul 06 13:50:07 BST 2011
[INFO] Final Memory: 24M/58M
[INFO]------------------------------------------------------------------
測試結果存在target/surefire-reports目錄下。現在你可以去看看那裡的文本文件。稍後你能在一個更棒的Web頁面上看到這些結果。
提示 你應該注意到了,命令中還有
clean
目標。我們這麼做是出於習慣,以防有遺留的殘餘物欺騙我們。
現在你的代碼編譯過,也測試過了,並且已經準備好打包了。儘管你可以直接用package
目標,但我們會用install
目標。想知道為什麼,且看下文分解!
4. 安裝
目標install
的任務主要有兩個。按pom.xml文件中<packaging>
指定的方式(此例中為JAR文件)對編譯結果打包。然後把打包好的工件安裝到本地Maven資源庫中(在$HOME/.m2/repository下),以便其他項目可以把它當做依賴項用。跟其他目標一樣,如果它發現之前的構建步驟還沒執行,它也會先執行這些相關目標。請執行下面的Maven命令:
mvn install
一旦完成install
目標,你應該見到下面這種輸出報告。
...
[INFO] [jar:jar {execution: default-jar}]
[INFO] Building jar: C:\Projects\workspace3.6\code\trunk\target\
java7developer-1.0.0.jar
[INFO] [install:install {execution: default-install}]
[INFO] Installing C:\Projects\workspace3.6\code\trunk\target\java7developer-1.0.0.jar
to C:\Documents and Settings\Admin\.m2\repository\com\java7developer\
java7develope
r\1.0.0\java7developer-1.0.0.jar
[INFO]------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO]------------------------------------------------------------------
[INFO] Total time: 17 seconds
[INFO] Finished at: Wed Jul 06 13:53:04 BST 2011
[INFO] Final Memory: 28M/66M
[INFO]------------------------------------------------------------------
在target目錄(package
目標的結果)和本地Maven資源庫的$HOME/.m2/repository/com.java7developer/1.0.0目錄下應該能找到java7developer-1.0.0.jar工件。
提示 你可能希望把Scala和Groovy代碼分別放到它們自己的JAR文件中。Maven支持這種操作,但你必須記住,對於Maven來說,每個獨立的JAR工件都應該對應一個獨立的項目。也就是說你必須用到Maven中多模塊項目的概念。請參見Maven的Guide to Working with Multiple Modules(多模塊處理指南)頁面瞭解細節(http://maven.apache.org/guides/mini/guide-multiple-modules.html1)。
1 短鏈接http://t.cn/zjIlX70。——譯者注
我們大多數人都是在團隊中工作,並且經常要共享代碼庫,那我們怎麼才能保證快速、可靠地構建大家共享的代碼呢?這要靠CI服務器來保證,並且到目前為止,對於Java開發人員來說現在最流行的CI服務器是Jenkins。