参考Maven入门指南,Maven Getting Started Guide,Maven Tutorial
Maven主要为开发者提供了一个可复用、可维护的工程模型,以及与基于这个模型的构建工具和一系列插件。
构建工具是将软件项目构建相关的过程自动化的工具。构建一个软件项目通常包含以下一个或多个过程:
- 生成源码(如果项目使用自动生成源码);
- 从源码生成项目文档;
- 编译源码;
- 将编译后的代码打包成JAR文件或者ZIP文件;
- 将打包好的代码安装到服务器、仓库或者其它的地方;
有些项目可能需要更多的过程才能完成构建,这些过程一般也可以整合到构建工具中,因此它们也可以实现自动化。
自动化构建过程的好处是将手动构建过程中犯错的风险降到最低。而且,自动构建工具通常要比手动执行同样的构建过程要快。
核心概念
pom.xml
Maven工程结构和内容被定义在一个xml文件中 - pom.xml,也就是project object model,此文件是整个Maven系统的基础组件,描述了项目的资源,源码,测试代码,依赖等。pom.xml位于项目的根目录下。
在执行一条maven命令时,maven会读取项目的pom.xml文件,并根据pom.xml的相关内容执行命令。
构建生命周期、阶段和目标
Maven的构建过程从大到小被分为构建生命周期、阶段和目标,其关系为:
- 一个生命周期包含若干个阶段
- 一个阶段包含若干个目标
一般maven使用的命令形式为:mvn command
,其中command命令就是构建生命周期、阶段或者目标的名字。如果command为一个生命周期,该生命周期的所有构建阶段都会被执行。如果command为一个生命周期具体的一个构建阶段,那么这个生命周期中,所有处于目标执行阶段之前的阶段以及该目标阶段都会被执行。
目标操作(goal)表示某个有助于项目构建和管理的特定的任务。
- 一个目标操作可以绑定0到多个构建阶段。
- 不绑定任何构建阶段的目标操作可以通过直接调用的方式执行。
执行的顺序取决于目标操作和构建阶段调用的次序。以下面的命令为例,参数clean和package是构建阶段而dependency:copy-dependencies是一个目标操作。
mvn clean dependency:copy-dependencies package
在这里,clean阶段会首先执行,然后是执行dependency:copy-dependencies目标操作,最后是执行package阶段。
依赖和仓库
一个大型项目一般都会需要依赖许多外部包,也就是这个项目的依赖。Maven设置了一个本地仓库,所有依赖都会被放在本地仓库(本地的一个目录)中。如果在本地仓库中不存在该依赖,则Maven会从中央仓库下载并放到本地仓库。
插件
Maven提供了一系列标准的构建生命周期和构建阶段、目标,如果仍无法满足项目构建的要求,可以使用查找并使用插件来满足需要。
Maven实际上是一个执行插件的框架,其所有的任务其实都是由插件完成。Maven插件通常用于:
- 生成jar包文件
- 生成war包文件
- 编译源码文件
- 代码单元测试
- 生成项目文档
- 生成项目报告
一个插件通常提供一系列的目标操作,并且目标操作可以通过以下格式的命令执行:mvn [插件名]:[目标操作名]
例如,一个Java项目可以通过运行下面的命令使用maven-compiler-plugin的compile目标操作编译。mvn compiler:compile
另外插件目标也可以与绑定到构建阶段上(可以是现有的构建阶段)。这样在执行该构建阶段时,所绑定的目标也会一起被执行。
例如在执行构建阶段时如果需要输出信息,可以使用maven-antrun-plugin插件来打印数据到控制台。
创建下面的pom.xml文件
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" |
执行mvn clean
.
Maven将会开始处理并且输出clean生命周期的clean阶段的信息。
1 | [INFO] Scanning for projects... |
上面的例子中阐明了下面几个关键概念:
- 插件在pom.xml文件中是通过plugins节点明确指定的。
- 每个插件可以有多个目标操作。
- 你可以使用phase节点来定义插件从哪个节点开始处理。我们已使用的是clean阶段。
- 你可以通过绑定任务到插件的目标操作来配置要执行的任务。我们已经绑定了echo任务到maven-antrun-plugin插件的run目标操作。
- 以上,Maven会处理剩下的事情。若本地仓库中找不到,Maven会下载插件,并且开始处理。
Maven提供了下面两类插件:
类型 | 描述 |
---|---|
构建插件(Build plugins) | 这类插件在构建过程中执行,并且应该配置在pom.xml文件的 |
报告插件(Reporting plugins) | 这类插件在生成站点过程中执行,并且应该配置在pom.xml文件的 |
下面是一些常用的插件的列表:
插件 | 描述 |
---|---|
clean | 构建完成后清理目标,删除目标目录。 |
compiler | 编译Java源文件。 |
surefile | 运行JUnit单元测试,生成测试报告。 |
jar | 从当前项目生成JAR文件。 |
war | 从当前项目生成WAR文件。 |
javadoc | 生成项目的Javadoc。 |
antrun | 运行任意指定构建阶段的一系列ant任务。 |
配置文件
配置文件用于以不同的方式构建项目。比如可能需要在本地环境构建,用于开发和测试,也可能需要构建后用于开发环境。具体可以配置的一些内容包括:
- 本地仓库的路径
- 当前编译配置选项等
maven设置了两个配置文件,分别在:
- maven安装目录下:
$M2_HOME/conf/settings.xml
- 用户主目录中:
${user.home}/.m2/settings.xml
如果两个文件都存在,则用户目录的配置会覆盖安装目下的配置。
在POM文件中增加不同的构建配置,可以启用不同的构建过程。当运行Maven时,可以指定要使用的配置。
POM
POM意为项目对象模型(Project Object Model),是Maven中基本单元。它是一个名为pom.xml的XML文件,总是存在于项目的更目录下。
POM包含了项目相关的信息和Maven用来构建一个或多个项目的各种配置详情。POM文件描述的是构建“什么”,而不是“如何”构建。如何构建是取决于Maven的构建阶段和目标。当然,如果需要,你也可以向Maven构建阶段中添加自定义的目标。
每一个项目都有一个POM文件。POM文件即pom.xml,应该放在项目的根目录下。一个项目如果分为多个子项目,一般来讲,父项目有一个POM文件,每一个子项目都有一个POM文件。在这种结构下,既可以一步构建整个项目,也可以各个子项目分开构建。
POM也包含了各种目标操作(goal)和插件。当执行一个任务或者目标操作时,Maven会查找当前目录下的POM。Maven读取POM,从中获得需要的配置信息,然后执行目标操作。部分Maven可以从POM中明确的配置列出如下:
- 项目依赖(project dependencies)
- 插件(plugins)
- 目标操作(goals)
- 构建(build profiles)
- 项目版本(project version)
- 开发者(developers)
- 邮件列表(mailing list)
一个最基本的POM文件如下:
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" |
groupID
表示开发者或者开发公司的唯一ID,一般使用java包的根名作为groupID。artifactId
一般是项目名。version
指项目版本。
注意,项目在maven仓库的结构化目中,该结构化目录会与groupID匹配,每一个.
会成为目录分隔符,每个词都表示一个目录。如groupID为com.plusaber
项目将位于MAVEN_REPO/com/plusaber
。类似的,artifactId
也会成为MAVEN_REPO/com/plusaber
的子目录,另外artifactId
也是构建完项目后生成的jar包的文件名的一部分。
- 父pom
所有的Maven pom文件都继承自一个父pom。如果没有指定父pom,则该pom文件继承自根pom。
可以在pom文件指定父pom:
1 | <project xmlns=”http://maven.apache.org/POM/4.0.0″ |
子pom的配置会覆盖父pom的配置,由于继承和覆盖的原因,无法直接通过查看子pom和父pom了解最后的有效pom。这时可以使用mvn help:effective-pom
打印出当前的有效pom文件。
1 |
|
在上面的完整的effective pom.xml中,你可以看到项目构建的所有信息,包括默认的源文件目录结构,输出路径,需要的插件,报告目录,这些信息Maven在执行期望的目标操作时都会用到。
Maven的pom.xml在大部分情况要求手动写。Maven提供了很多archetype插件用来创建项目以及按规则创建项目结构和pom.xml文件。
Maven命令
mvn command
,其中command命令就是构建生命周期、阶段或者目标的名字。如果command为一个生命周期,该生命周期的所有构建阶段都会被执行。如果command为一个生命周期具体的一个构建阶段,那么这个生命周期中,所有处于目标执行阶段之前的阶段以及该目标阶段都会被执行。mvn install
install
是默认生命周期的一个阶段,具体任务包括编译项目,将打包的jar文件复制到本地仓库。事实上,在执行install之前,默认生命周期位于install的所有阶段都被执行。
我们可以向mvn命令传入多个参数,执行多个构建周期或阶段,如:mvn clean install
clean是一个生命周期(简称周期),具体任务是删除maven输入目录中已编译的类文件,然后执行install构建阶段。一个一个顺序执行,互相没有影响。
也可以执行一个maven目标,这时参数需要将构建阶段与目标名以冒号相连。mvn dependency:copy-dependencies
,执行dependency
构建阶段中的copy-dependencies
目标。
Maven目录结构
maven的一个主要原则就是约定优先,maven设置了一个标准的目录结构,如果在项目中遵循Maven的目录结构,就无需在pom文件中指定源码测试代码等目录。
dir/file | usage |
---|---|
src/main/java Application/Library | sources |
src/main/resources | Application/Library resources |
src/main/filters | Resource filter files |
src/main/webapp | Web application sources |
src/test/java Test | sources |
src/test/resources | Test resources |
src/test/filters Test | resource filter files |
src/it | Integration Tests (primarily for plugins) |
src/assembly | Assembly descriptors |
src/site | Site |
LICENSE.txt | Project’s license |
NOTICE.txt | Notices and attributions required by libraries that the project depends on |
README.txt | Project’s readme |
依赖
项目依赖
Maven提供了依赖管理的功能。你只需要在pom文件里指定依赖jar包的名称、版本号,Maven会自动下载并放到你的Maven本地仓库中。如果这些外部jar包依赖了其它的库,它们也会被下载到你的Maven本地仓库。
可以通过在pom文件添加dependencies属性中指定项目依赖。
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" |
1 | MAVEN_REPOSITORY_ROOT/junit/junit/4.11 |
可以看到,每一个依赖也是由groupID,artifactId和version来描述,和pom文件开头用来标识项目的方式是一样的。
执行这个pom文件时,如果本地仓库已经有了这两个依赖,maven就不会去中央仓库下载(本地仓库的依赖可以被多个项目使用)。如果没有,maven则会去中央仓库寻找这些依赖并下载到本地仓库。
需要注意的是,有时候指定的依赖在maven的中央仓库里没有,这是我们需要手动下载这些依赖并放到mavne的本地仓库。这些依赖必须放到与groupId, artifactId和version匹配的字目录中,用/
替换.
并为每个字段创建相应目录。
外部依赖
Maven的外部依赖指的是不在Maven的仓库(包括本地仓库、中央仓库和远程仓库)中的依赖(jar包)。它可能位于你本地硬盘的某个地方,比如web应用的lib目录下。
可以配置外部依赖如下:
1 | <dependency> |
groupId和artifactId为依赖的名称,即API的名称。scope属性为system。systemPath属性为jar文件的路径。${basedir}为pom文件所在的目录,路径中的其它部分是相对于该目录而言的。
快照依赖
快照依赖指的是那些还在开发中的依赖(jar包)。与其经常地更新版本号来获取最新版本,不如你直接依赖项目的快照版本。快照版本的每一个build版本都会被下载到本地仓库,即使该快照版本已经在本地仓库了。总是下载快照依赖可以确保本地仓库中的每一个build版本都是最新的。
在pom文件的最开头(设置groupId和artifactId的地方),在版本号后追加-SNAPSHOT,则告诉Maven你的项目是一个快照版本。如:
<version>1.0-SNAPSHOT</version>
在配置依赖时,在版本号后追加-SNAPSHOT表明依赖的是一个快照版本。如:
1 | <dependency> |
追加在version后的-SNAPSHOT告诉Maven这是一个快照版本。
Maven仓库
Maven仓库就是存储jar包和一些元数据信息的目录,一般是用于根目录下的.m2
文件夹,比如/Users/admin/.m2
。其中的元数据即pom文件,描述了该jar包属于哪个项目,以及jar包所需的外部依赖。该元数据信息使得Maven可以递归地下载所有的依赖,直到整个依赖树都下载完毕并放到你的本地仓库中。参考Introduction to Repositories。
Maven有三种类型的仓库:
- 本地仓库
- 中央仓库
- 远程仓库
Maven根据以上的顺序去仓库中搜索依赖。首先是本地仓库,然后是中央仓库,最后,如果pom文件中配置了远程仓库,则会去远程仓库中查找。
本地仓库
本地仓库就是开发者电脑上的一个目录。该仓库包含了Maven下载的所有依赖。一般来讲,一个本地仓库为多个不同的项目服务。因此,Maven只需下载一次,即使有多个项目都依赖它(如junit)。
通过mvn install命令可以将你自己的项目构建并安装到本地仓库中。这样,你的其它项目就可以通过在pom文件将该jar包作为外部依赖来使用。
Maven的本地仓库默认在你本机的用户目录下。不过,你可以在Maven的配置文件中修改本地仓库的路径。Maven的配置文件也在用户目录下(user-home/.m2),文件名为settings.xml。以下示例为本地仓库指定其它的路径:
1 | <settings> |
中央仓库
Maven的中央仓库由Maven社区提供。默认情况下,所有不在本地仓库中的依赖都会去这个中央仓库查找。然后Maven会将这些依赖下载到你的本地仓库。访问中央仓库不需要做额外的配置。
远程仓库
远程仓库是位于web服务器上的一个仓库,Maven可以从该仓库下载依赖,就像从中央仓库下载依赖一样。远程仓库可以位于Internet上的任何地方,也可以是位于本地网络中。
远程仓库一般用于放置组织内部的项目,该项目由多个项目共享。比如,由多个内部项目共用的安全项目。该安全项目不能被外部访问,因此不能放在公开的中央仓库下,而应该放到内部的远程仓库中。
远程仓库中的依赖也会被Maven下载到本地仓库中。可以在pom文件里配置远程仓库。将以下的xml片段放到属性之后:
1 | <repositories> |
Maven的构建生命周期、阶段和目标
当使用Maven构建项目时,会遵循一个构建生命周期。该生命周期分为多个构建阶段,而构建阶段又分为多个构建目标。参考Introduction to the Build Lifecycle
构建生命周期
Maven有三个内嵌的构建生命周期:clean
, default
, site
.
每一个构建生命期关注项目构建的不同方面。因此,它们是独立地执行的。Maven可以执行多个生命期,但是它们是串行执行的,相互独立,就像你执行了多条独立的Maven命令。
Clean生命周期
当我们执行mvn的post-clean命令时,Maven调用clean生命周期,它包含以下阶段:
- pre-clean
- clean
- post-clean
Maven的clean目标操作(clean:clean)在clean生命周期中被绑定到clean阶段。clean:clean 目标操作通过删除构建目录来删除一个项目构建的输出文件。这样,当mvn clean命令执行时,Maven会删除构建目录。
default生命周期
构建生命周期是Maven的最主要的生命周期,用来构建应用。default生命期关注的是项目的编译和打包。clean生命期关注的是从输出目录中删掉临时文件,包括自动生成的源文件、编译后的类文件,之前版本的jar文件等。site生命期关注的是为项目生成文档。实际上,site可以使用文档为项目生成一个完整的网站。
构建阶段
每一个构建生命期被分为一系列的构建阶段,构建阶段又被分为构建目标。因此,整个构建过程由一系列的构建生命期、构建阶段和构建目标组成。
你可以执行一个构建生命期,如clean或site,一个构建阶段,如default生命期的install,或者一个构建目标,如dependency:copy-dependencies。注意:你不能直接执行default生命期,你需要指定default生命期中的一个构建阶段或者构建目标。
当你执行一个构建阶段时,所有在该构建阶段之前的构建阶段(根据标准构建顺序)都会被执行。因此,执行install阶段,意味着所有位于install阶段前的构建阶段都会被执行,然后才执行install阶段。
default生命期更多的关注于构建代码。由于你不能直接执行default生命期,你需要执行其中一个构建阶段或者构建目标。default生命期包含了相当多的构建阶段和目标,这里不会所有都介绍。最常用的构建阶段有:
validate
- validate the project is correct and all necessary information is availablecompile
- compile the source code of the projecttest
- test the compiled source code using a suitable unit * testing framework. These tests should not require the code be packaged or deployedpackage
- take the compiled code and package it in its distributable format, such as a JAR.integration-test
- process and deploy the package if necessary into an environment where integration tests can be runverify
- run any checks to verify the package is valid and meets quality criteriainstall
- install the package into the local repository, for use as a dependency in other projects locallydeploy
- done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects.
完整的default周期的阶段:
生命周期阶段 | 描述 |
---|---|
validate(校验) | 校验项目是否正确并且所有必要的信息可以完成项目的构建过程。 |
initialize(初始化) | 初始化构建状态,比如设置属性值。 |
generate-sources(生成源代码) | 生成包含在编译阶段中的任何源代码。 |
process-sources(处理源代码) | 处理源代码,比如说,过滤任意值。 |
generate-resources(生成资源文件) | 生成将会包含在项目包中的资源文件。 |
process-resources (处理资源文件) | 复制和处理资源到目标目录,为打包阶段最好准备。 |
compile(编译) | 编译项目的源代码。 |
process-classes(处理类文件) | 处理编译生成的文件,比如说对Java class文件做字节码改善优化。 |
generate-test-sources(生成测试源代码) | 生成包含在编译阶段中的任何测试源代码。 |
process-test-sources(处理测试源代码) | 处理测试源代码,比如说,过滤任意值。 |
test-compile(编译测试源码) | 编译测试源代码到测试目标目录. |
process-test-classes(处理测试类文件) | 处理测试源码编译生成的文件。 |
test(测试) | 使用合适的单元测试框架运行测试(Juint是其中之一)。 |
prepare-package(准备打包) | 在实际打包之前,执行任何的必要的操作为打包做准备。 |
package(打包) | 将编译后的代码打包成可分发格式的文件,比如JAR、WAR或者EAR文件。 |
pre-integration-test(集成测试前) | 在执行集成测试前进行必要的动作。比如说,搭建需要的环境。 |
integration-test(集成测试) | 处理和部署项目到可以运行集成测试环境中。 |
post-integration-test(集成测试后) | 在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。 |
verify (验证) | 运行任意的检查来验证项目包有效且达到质量标准。 |
install(安装) | 安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖。 |
deploy(部署) | 将最终的项目包复制到远程仓库中与其他开发者和项目共享。 |
构建目标
构建目标是Maven构建过程中最细化的步骤。一个目标可以与一个或多个构建阶段绑定,也可以不绑定。如果一个目标没有与任何构建阶段绑定,你只能将该目标的名称作为参数传递给mvn命令来执行它。如果一个目标绑定到多个构建阶段,该目标在绑定的构建阶段执行的同时被执行。
Maven构建配置
Maven构建配置使你能使用不同的配置来构建项目。不用创建两个独立的pom文件。你只需使用不同的构建配置指定不同的配置文件,然后使用该配置文件构建项目即可。
关于构建配置的详细信息可以参考Maven POM参考的Profile部分。这里是一个快速的介绍。
Maven的构建配置在pom文件的profiles属性中指定,例如:
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" |
构建配置描述的是当使用该配置构建项目时,对pom文件所做的修改,比如修改应用使用的配置文件等。profile属性中的值将会覆盖其上层的、位于pom文件中的配置。
在profile属性中,有一个activation子属性。该属性指定了启用该构建配置的条件。选择构建配置的一种方式是在settings.xml文件中指定;另一种方式是在Maven命令行使用-P profile-name
指定。更多细节参考构建配置的文档。
Maven插件
使用Maven插件,可以向构建过程添加自定义的动作。创建一个简单的Java类,该类继承一个特殊的Maven类,然后为项目创建一个pom文件。该插件应该位于其项目下。可以参考Plugin Developers Centre。