还记得图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> //①工件的唯一ID <scope>compile</scope> //②编译作用域 </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> //③测试作用域 </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.8.5</version> <scope>test</scope> </dependency> ...</dependencies>
为了让Maven找到你引用的工件,需要给它正确的<groupId>
、<artifactId>
和<version>
①。我们在之前提到过,把<scope>
设置为compile
②会把这些JAR加到CLASSPATH中用于代码的编译。将<scope>
设置为test
③会在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_CODEmvn 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.TicketRevenueTestTests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 secRunning com.java7developer.chapter11.listing_11_4.TicketRevenueTestTests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 secRunning com.java7developer.chapter11.listing_11_5.TicketTestTests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.015 secResults :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.jarto C:/Documents and Settings/Admin/.m2/repository/com/java7developer/java7developer/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。