Maven入门指南
Maven介绍:
Maven是用Java开发的一个强大的Java项目构建工具。当然,你也可以使用其它工具来构建项目,但由于Maven是用Java开发的,因此Maven被更多的用于Java项目中。
这篇教程的目的是帮助你理解Maven的工作机制。因此教程主要关注Maven的核心概念。一旦你理解了这些核心概念,当你想了解更多的细节时,再取查看Maven文档,或者从网上搜索,就变得容易多了
本教程基于Maven3.0.5。Maven网站的地址为:http://maven.apache.org,你可以从该网站上下载最新版的Maven,并关注项目的进展。
什么是构建工具?
构建工具是将软件项目构建相关的过程自动化的工具。构建一个软件项目通常包含以下一个或多个过程:
生成源码(如果项目使用自动生成源码);
从源码生成项目文档;
编译源码;
将编译后的代码打包成JAR文件或者ZIP文件;
将打包好的代码安装到服务器、仓库或者其它的地方;
有些项目可能需要更多的过程才能完成构建,这些过程一般也可以整合到构建工具中,因此它们也可以实现自动化。
自动化构建过程的好处是将手动构建过程中犯错的风险降到最低。而且,自动构建工具通常要比手动执行同样的构建过程要快。
安装Maven
安装Maven,访问Maven下载页,然后按照安装指南的步骤即可。总结一下,你需要做:
1.下载并解压Maven;
2.将环境变量M2_HOME设置为解压后的目录;
3.将M2环境变量设置为M2_HOME/bin(在Windows上是%M2_HOME%/bin,在Unix上是$M2_HOME/bin);
4.将M2添加到PATH环境变量中(Windows上是%M2%,Unix上是$M2);
5.打开终端输入`mvn`(不带引号),然后回车;
输入‘mvn’命令后,终端上回显示错误信息。不要担心这个错误。因为你没有给Maven传入pom文件,因此出现该错误信息是意料之中的。显示Maven错误信息说明Maven已经安装好了。
注意:Maven运行需要Java环境,因此也需要安装Java,Java版本1.5及以上;
Maven概览-核心概念
Maven的中心思想是POM文件(项目对象模型)。POM文件是以XML文件的形式表述项目的资源,如源码、测试代码、依赖(用到的外部Jar包)等。POM文件应该位于项目的根目录下。
下图说明了Maven是如何使用POM文件的,以及POM文件的主要组成部分:
maven-overview-1
这些概念先简单地解释一下,更多的细节放在本教程的具体小节中。
POM文件
当你执行一条Maven命令的时候,你会传入一个pom文件。Maven会在该pom文件描述的资源上执行该命令。
构建生命周期、阶段和目标
Maven的构建过程被分解为构建生命周期、阶段和目标。一个构建周期由一系列的构建阶段组成,每一个构建阶段由一系列的目标组成。当你运行Maven的时候,你会传入一条命令。这条命令就是构建生命周期、阶段或目标的名字。如果执行一个生命周期,该生命周期内的所有构建阶段都会被执行。如果执行一个构建阶段,在预定义的构建阶段中,所有处于当前构建阶段之前的阶段也都会被执行。
依赖和仓库
Maven执行时,其中一个首要目标就是检查项目的依赖。依赖是你的项目用到的jar文件(java库)。如果在本地仓库中不存在该依赖,则Maven会从中央仓库下载并放到本地仓库。本地仓库只是你电脑硬盘上的一个目录。你可以根据需要制定本地仓库的位置。你也可以指定下载依赖的远程仓库的地址。这些将会在后续的小节中详细介绍。
插件
构建插件可以向构建阶段中增加额外的构建目标。如果Maven标准的构建阶段和目标无法满足项目构建的需求,你可以在POM文件里增加插件。Maven有一些标准的插件供选用,如果需要你可以自己实现插件。
配置文件
配置文件用于以不同的方式构建项目。比如,你可能需要在本地环境构建,用于开发和测试,你也可能需要构建后用于开发环境。这两个构建过程是不同的。在POM文件中增加不同的构建配置,可以启用不同的构建过程。当运行Maven时,可以指定要使用的配置。
Maven与Ant
Ant是Apache另一个流行的构建工具。如果你熟悉Ant,正在学习Maven,你将会注意到两者在方法上的区别。
Ant使用命令式的方式,即你需要在Ant构建文件里指定Ant应该执行的操作。你可以指定低级别的操作,如复制文件、编译代码等。你指定操作,还需要执行这些操作执行的顺序。Ant没有默认的目录结构。
Maven使用声明式的方式,即你需要在POM文件里指定做什么,而不是如何做。POM文件描述项目的资源-而不是如何构建。相比而言,Ant构建文件描述的是如何构建项目。在Maven里,如何构建是在“Maven构建声明周期、阶段和目标”中预定义的。
MavenPOM文件
Maven的POM文件是一个xml文件,描述项目用到的资源,包括源代码目录、测试代码目录等的位置,以及项目依赖的外部jar包。
POM文件描述的是构建“什么”,而不是“如何”构建。如何构建是取决于Maven的构建阶段和目标。当然,如果需要,你也可以向Maven构建阶段中添加自定义的目标。
每一个项目都有一个POM文件。POM文件即pom.xml,应该放在项目的根目录下。一个项目如果分为多个子项目,一般来讲,父项目有一个POM文件,每一个子项目都有一个POM文件。在这种结构下,既可以一步构建整个项目,也可以各个子项目分开构建。
本文剩下的章节,主要介绍POM文件最重要的部分。POM文件的完整文档,参考[MavenPOMReference](http://maven.apache.org/pom.html)
如下为一个最小化的POM文件示例:
<projectxmlns=”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/xsd/maven-4.0.0.xsd”>
<modelVersion>4.0.0</modelVersion>
<groupId>com.jenkov</groupId>
<artifactId>java-web-crawler</artifactId>
<version>1.0.0</version>
</project>
*modelVersion*属性表示使用的POM模型的版本。选择和你正在使用的Maven版本一致的版本即可。版本4.0.0适用于Maven2和3。
*groupId”属性是一个组织或者项目(比如开源项目)的唯一ID。大多数情况下,你会使用项目的java包的根名称作为groupID。例如,对于我自己的Java网络爬虫项目,我会使用com.jenkov作为groupID。如果这个项目是一个由很多独立的贡献者组成的开源项目,也许选择与项目相关的名称作为groupID,比选择与我的公司相关的名称名作为groupID要合理地多。因此,选择com.javawebcrawler作为groupID。
groupId不一定非要使用Java的包名,也不一定要使用.分隔符来分隔ID中的词。但是,如果你这么使用,项目将会位于Maven仓库的结构化目录中,该结构化目录与groupID匹配。每一个.是一个目录分隔符,每一个词都表示一个目录。groupID为com.jenkov的项目将位于目录MAVEN_REPO/com/jenkov中。目录路径中的MAVEN_REPO表示Maven仓库的路径。
*artifactId*属性包含你正在构建的项目的名称。以我的Java网络爬虫项目来说,artifactID为java-web-crawler。artifactID是Maven仓库中groupID目录下的子目录名。artifactID也是构建完项目后生成的jar包的文件名的一部分。构建过程的输出,即构建结果,在Maven中成为构件(artifact)。通常它就是一个jar包、war包或者EAR包,蛋它也可以是别的。
*versionId包含项目的版本号。如果你的项目有不同的发行版,比如开源API,对构建过程版本化是很有用的。如果使用版本,项目的用户就可以查看项目的具体版本。版本号是artifactID目录下的子目录名。版本号也用作构建结果名称的一部分。(即jar包文件名的一部分–译者注)
上文中的groupId,artifactId和version属性,在项目构建后会生成一个jar文件,位于Maven仓库的如下路径中(目录和文件名):MAVEN_REPO/com/jenkov/java-web-crawler/1.0.0/java-web-crawler-1.0.0.jar
如果你的项目使用[Maven目录结构](http://tutorials.jenkov.com/maven/maven-tutorial.html#maven-directory-structure),而且项目没有外部依赖,上面的最简化POM文件就是你构建项目所需的所有配置了。
如果你的项目不遵从标准的目录结构,有外部依赖或者在构建过程中需要加入额外操作,你需要向POM文件中添加更多的配置。更多的配置查阅[MavenPOM参考](链接在上文)。
通常,你可以向POM文件中增加各种配置,这些配置告诉Maven如何更好地构建你的项目。查阅Maven的POM参考,了解更多的配置。
父pom
所有的Mavenpom文件都继承自一个父pom。如果没有指定父pom,则该pom文件继承自根pom。pom文件的继承关系如下图所示:
super-pom
可以让一个pom文件显式地继承另一个pom文件。这样,可以通过修改公共父pom文件的设置来修改所有子pom文件的设置。在pom文件的起始处指定父pom,例如:
<projectxmlns=”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/xsd/maven-4.0.0.xsd”>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.codehaus.mojo</groupId>
<artifactId>my-parent</artifactId>
<version>2.0</version>
<relativePath>../my-parent</relativePath>
</parent>
<artifactId>my-project</artifactId>
…
</project>
子pom文件的设置可以覆盖父pom文件的设置,只需要在子pom文件里指定新的设置即可。
关于pom文件继承更详细的内容可以参考MavenPOM文档。
有效pom
考虑到pom文件的继承关系,当Maven执行的时候可能很难确定最终的pom文件的内容。总的pom文件(所有继承关系生效后)被称为有效pom(effectivepom)。可以使用以下的命令让Maven打印出当前的有效pom:
mvnhelp:effective-pom
执行以上命令,Maven会将有效pom输出到命令行。
Maven配置文件
Maven有两个配置文件。配置文件里的设置,对所有的pom文件都是有效的。比如,你可以配置:
本地仓库的路径;
当前的编译配置选项
等等
配置文件名为settings.xml,两个配置文件分别为:
+Maven安装目录中:$M2_HOME/conf/settings.xml
+用户主目录中:${user.home}/.m2/settings.xml
两个配置文件都是可选的。如果两个文件都存在,则用户目录下的配置会覆盖Maven安装目录中的配置。
关于Maven配置文件,参考[Maven配置文档](http://maven.apache.org/settings.html)
让Maven跑起来
当你安装好了Maven,并且在项目的根目录下创建了POM文件,可以在项目上运行Maven了。
运行Maven只需在命令行执行`mvn`命令即可。当执行`mvn`命令时,将构建周期、阶段或目标作为参数传进去,Maven就会执行它们。例如:
mvninstall
该命令执行`install`阶段(是默认构建阶段的一部分),编译项目,将打包的JAR文件复制到本地的Maven仓库。事实上,该命令在执行install之前,会执行在构建周期序列中位于install之前的所有阶段。
你可以向mvn命令传入多个参数,执行多个构建周期或阶段,如:
mvncleaninstall
该命令首先执行clean构建周期,删除Maven输出目录中已编译的类文件,然后执行install构建阶段。
也可以执行一个Maven目标(构建阶段的一部分),将构建阶段与目标名以冒号(:)相连,作为参数一起传给Maven命令。例如:
mvndependency:copy-dependencies
该命令执行`dependency`构建阶段中的`copy-dependencies`目标。
Maven目录结构
Maven有一个标准的目录结构。如果你在项目中遵循Maven的目录结构,就无需在pom文件中指定源代码、测试代码等目录。
Maven的目录结构布局,参考Maven标准目录结构介绍
以下为最重要的目录:
-src
-main
-java
-resources
-webapp
-test
-java
-resources
-target
src目录是源代码和测试代码的根目录。main目录是应用的源代码目录。test目录是测试代码的目录。main和test下的java目录,分别表示应用的java源代码和测试代码。
resources目录包含项目的资源文件,比如应用的国际化配置的属性文件等。
如果是一个web项目,则webapp目录为web项目的根目录,其中包含如WEB-INF等子目录。
target目录是由Maven创建的,其中包含编译后的类文件、jar文件等。当执行maven的clean目标后,target目录会被清空。
项目依赖
除非你的项目很小,否则会需要外部的JavaAPI或者框架以jar包形式提供的依赖。当你编译项目代码的时候,需要classpath上的这些jar包。
使项目依赖的外部jar包以正确的版本保持最新状态,是一项艰巨的任务。而且,这些外部的jar包还会依赖其它的外部jar包。递归地下载所有这些外部依赖(jar包),并且要确保下载的版本都是正确的,是非常麻烦的。尤其是当项目变得越来越大,外部依赖越来越多的时候。
幸运的是,Maven内嵌有依赖管理的功能。你只需要在pom文件里指定依赖jar包的名称、版本号,Maven会自动下载并放到你的Maven本地仓库中。如果这些外部jar包依赖了其它的库,它们也会被下载到你的Maven本地仓库。
在pom文件的dependencies属性中指定项目依赖,如:
<projectxmlns="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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jenkov.crawler</groupId>
<artifactId>java-web-crawler</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
</build>
</project>
在dependencies属性下有两个dependency子属性,每一个dependency属性描述了一个外部依赖。
每一个依赖由groupId,artifactId和version来描述。如果你有印象,这和我们在pom文件的开头用来标识项目的方式是一样的。上面的示例表明,项目的依赖为org.jsoup组织下的1.7.1版本的jsoup,以及junit组织下的4.8.1版本的junit。
当Maven执行该pom文件时,将从Maven的中央仓库里下载这两个jar包并放到Maven的本地仓库。如果本地仓库中已经有了这两个依赖,Maven就不会去下载了。只有本地仓库中没有的依赖才会被下载。
有的时候,指定的依赖在Maven的中央仓库里没有。你可以直接下载这些依赖,然后放到Maven的本地仓库。这些依赖必须放到与groupId、artifactId和version匹配的子目录中。用/替换所有的点(.)并且使用/分隔groupId、artifactId和version,这就是与该依赖匹配的子目录。
上面示例中的两个依赖将会被放到以下子目录中:
MAVEN_REPOSITORY_ROOT/junit/junit/4.8.1
MAVEN_REPOSITORY_ROOT/org/jsoup/jsoup/1.7.1
外部依赖
Maven的外部依赖指的是不在Maven的仓库(包括本地仓库、中央仓库和远程仓库)中的依赖(jar包)。它可能位于你本地硬盘的某个地方,比如web应用的lib目录下。这里的“外部”是对Maven仓库系统而言的,不仅仅是对项目而言的。大部分的外部依赖都是针对项目的,很少的外部依赖是针对仓库系统的(即不在仓库中)。
配置外部依赖的示例如下:
<dependency>
<groupId>mydependency</groupId>
<artifactId>mydependency</artifactId>
<scope>system</scope>
<version>1.0</version>
<systemPath>${basedir}\war\WEB-INF\lib\mydependency.jar</systemPath>
</dependency>
groupId和artifactId为依赖的名称,即API的名称。scope属性为system。systemPath属性为jar文件的路径。${basedir}为pom文件所在的目录,路径中的其它部分是相对于该目录而言的。
快照依赖
快照依赖指的是那些还在开发中的依赖(jar包)。与其经常地更新版本号来获取最新版本,不如你直接依赖项目的快照版本。快照版本的每一个build版本都会被下载到本地仓库,即使该快照版本已经在本地仓库了。总是下载快照依赖可以确保本地仓库中的每一个build版本都是最新的。
在pom文件的最开头(设置groupId和artifactId的地方),在版本号后追加-SNAPSHOT,则告诉Maven你的项目是一个快照版本。如:
<version>1.0-SNAPSHOT</version>
可以看到加到版本号后的-SNAPSHOT。
在配置依赖时,在版本号后追加-SNAPSHOT表明依赖的是一个快照版本。如:
<dependency>
<groupId>com.jenkov</groupId>
<artifactId>java-web-crawler</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
追加在version后的-SNAPSHOT告诉Maven这是一个快照版本。
可以在Maven配置文件中设置快照版本下载的频率。
Maven仓库
Maven仓库就是存储jar包和一些元数据信息的目录。其中的元数据即pom文件,描述了该jar包属于哪个项目,以及jar包所需的外部依赖。该元数据信息使得Maven可以递归地下载所有的依赖,直到整个依赖树都下载完毕并放到你的本地仓库中。
Maven仓库的详细介绍参考Maven仓库介绍,快速介绍如下:
Maven有三种类型的仓库:
本地仓库
中央仓库
远程仓库
Maven根据以上的顺序去仓库中搜索依赖。首先是本地仓库,然后是中央仓库,最后,如果pom文件中配置了远程仓库,则会去远程仓库中查找。
下图说明了三种仓库的类型以及位置:
maven-repo-types-loc
本地仓库
本地仓库就是开发者电脑上的一个目录。该仓库包含了Maven下载的所有依赖。一般来讲,一个本地仓库为多个不同的项目服务。因此,Maven只需下载一次,即使有多个项目都依赖它(如junit)。
通过mvninstall命令可以将你自己的项目构建并安装到本地仓库中。这样,你的其它项目就可以通过在pom文件将该jar包作为外部依赖来使用。
Maven的本地仓库默认在你本机的用户目录下。不过,你可以在Maven的配置文件中修改本地仓库的路径。Maven的配置文件也在用户目录下(user-home/.m2),文件名为settings.xml。以下示例为本地仓库指定其它的路径:
<settings>
<localRepository>
d:\data\java\products\maven\repository
</localRepository>
</settings>
中央仓库
Maven的中央仓库由Maven社区提供。默认情况下,所有不在本地仓库中的依赖都会去这个中央仓库查找。然后Maven会将这些依赖下载到你的本地仓库。访问中央仓库不需要做额外的配置。
远程仓库
远程仓库是位于web服务器上的一个仓库,Maven可以从该仓库下载依赖,就像从中央仓库下载依赖一样。远程仓库可以位于Internet上的任何地方,也可以是位于本地网络中。
远程仓库一般用于放置组织内部的项目,该项目由多个项目共享。比如,由多个内部项目共用的安全项目。该安全项目不能被外部访问,因此不能放在公开的中央仓库下,而应该放到内部的远程仓库中。
远程仓库中的依赖也会被Maven下载到本地仓库中。
可以在pom文件里配置远程仓库。将以下的xml片段放到属性之后:
<repositories>
<repository>
<id>jenkov.code</id>
<url>http://maven.jenkov.com/maven2/lib</url>
</repository>
</repositories>
Maven的构建生命周期、阶段和目标
当使用Maven构建项目时,会遵循一个构建生命周期。该生命周期分为多个构建阶段,而构建阶段又分为多个构建目标。Maven的构建周期、构建阶段和目标的更多细节请参考Maven构建周期介绍,这里是一个快速介绍。
构建生命周期
Maven有三个内嵌的构建生命周期:
default
clean
site
每一个构建生命期关注项目构建的不同方面。因此,它们是独立地执行的。Maven可以执行多个生命期,但是它们是串行执行的,相互独立,就像你执行了多条独立的Maven命令。
default生命期关注的是项目的编译和打包。clean生命期关注的是从输出目录中删掉临时文件,包括自动生成的源文件、编译后的类文件,之前版本的jar文件等。site生命期关注的是为项目生成文档。实际上,site可以使用文档为项目生成一个完整的网站。
构建阶段
每一个构建生命期被分为一系列的构建阶段,构建阶段又被分为构建目标。因此,整个构建过程由一系列的构建生命期、构建阶段和构建目标组成。
你可以执行一个构建生命期,如clean或site,一个构建阶段,如default生命期的install,或者一个构建目标,如dependency:copy-dependencies。注意:你不能直接执行default生命期,你需要指定default生命期中的一个构建阶段或者构建目标。
当你执行一个构建阶段时,所有在该构建阶段之前的构建阶段(根据标准构建顺序)都会被执行。因此,执行install阶段,意味着所有位于install阶段前的构建阶段都会被执行,然后才执行install阶段。
default生命期更多的关注于构建代码。由于你不能直接执行default生命期,你需要执行其中一个构建阶段或者构建目标。default生命期包含了相当多的构建阶段和目标,这里不会所有都介绍。最常用的构建阶段有:
构建阶段描述
validate验证项目的正确性,以及所有必需的信息都是否都存在。同时也会确认项目的依赖是否都下载完毕。
compile编译项目的源代码
test选择合适的单元测试框架,对编译后的源码执行测试;这些测试不需要代码被打包或者部署。
package将编译后的代码以可分配的形式打包,如Jar包。
install将项目打包后安装到本地仓库,可以作为其它项目的本地依赖。
deploy将最终的包复制到远程仓库,与其它开发者和项目共享。
将构建阶段的名称作为参数传给mvn命令,就是执行该构建阶段,如:
mvnpackage
该示例执行package构建阶段,因此在Maven的构建阶段序列中所有位于该阶段之前的阶段也都会被执行。
如果标准的Maven构建阶段和目标无法满足项目的需要,可以创建Maven插件增加你需要的构建功能。
构建目标
构建目标是Maven构建过程中最细化的步骤。一个目标可以与一个或多个构建阶段绑定,也可以不绑定。如果一个目标没有与任何构建阶段绑定,你只能将该目标的名称作为参数传递给mvn命令来执行它。如果一个目标绑定到多个构建阶段,该目标在绑定的构建阶段执行的同时被执行。
Maven构建配置
Maven构建配置使你能使用不同的配置来构建项目。不用创建两个独立的pom文件。你只需使用不同的构建配置指定不同的配置文件,然后使用该配置文件构建项目即可。
关于构建配置的详细信息可以参考MavenPOM参考的Profile部分。这里是一个快速的介绍。
Maven的构建配置在pom文件的profiles属性中指定,例如:
<projectxmlns="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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jenkov.crawler</groupId>
<artifactId>java-web-crawler</artifactId>
<version>1.0.0</version>
<profiles>
<profile>
<id>test</id>
<activation>...</activation>
<build>...</build>
<modules>...</modules>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<dependencies>...</dependencies>
<reporting>...</reporting>
<dependencyManagement>...</dependencyManagement>
<distributionManagement>...</distributionManagement>
</profile>
</profiles>
</project>
构建配置描述的是当使用该配置构建项目时,对pom文件所做的修改,比如修改应用使用的配置文件等。profile属性中的值将会覆盖其上层的、位于pom文件中的配置。
在profile属性中,有一个activation子属性。该属性指定了启用该构建配置的条件。选择构建配置的一种方式是在settings.xml文件中指定;另一种方式是在Maven命令行使用-Pprofile-name指定。更多细节参考构建配置的文档。
Maven插件
使用Maven插件,可以向构建过程添加自定义的动作。创建一个简单的Java类,该类继承一个特殊的Maven类,然后为项目创建一个pom文件。该插件应该位于其项目下。