Apache Maven (有时简称为Maven)是Apache软件基金会开发的一个开源的、用于自动构建项目的工具。该工具可用于自动构建任何编程语言编写的工程。但是,由于一些历史原因,也或许因为Maven是由Java语言开发的,所以,Maven被主要用于构建Java工程。
Maven系统建立于工程对象模型(Project Object Model, or POM)的概念之上;它能够自动构建项目,自动生成项目报告,和自动生成项目文档等功能。Maven还能自动解析第三方库之间的依赖关系;自动地从Maven库(Maven Repository)获取第三方库(3rd-Party Libraries);完成项目构建。因此,使用Maven能极大的减少开发人员和维护人员的工作量。
因为Maven本身是用Java语言开发的,所以需要安装Java运行环境(Java Runtime Environment, or JRE)才能运行Maven工具。
每个Maven项目的所有内容都是在一个pom.xml文件里描述的。该文件使用XML格式语言描述与该项目相关的信息(包括项目名称、项目描述、项目版本等)。一般的,pom.xml文件放在工程的根目录下,该文件包含一个根节点<project>来表示该项目。
由Maven管理的Java项目的结构如下。在项目的根目录下有pom.xml项目管理文件,和src和target两个目录。src目录下存放的是源代码文件;target目录下存放的是项目构建的输出文件(例如:jar文件)。target目录可由Maven自动创建。
在src目录下有main和test两个目录。main目录存放的是功能性的源代码文件;而test目录下存放的是项目的测试源代码文件。这些文件用于测试src/main目录下的源代码。
在main和test目录下分别有java和resources两个目录。其中,java目录存放的是项目的源代码文件(.java文件);resources目录存放的是项目的配置文件(例如:日志配置文件,或者项目的配置文件)。在打包过程中,resources目录下的文件会自动打包在.jar或者.war文件中。在运行时,程序也能找到并且读取resources目录下的文件。
当运行Maven构建该项目时,Maven会读取pom.xml文件中的内容,并按照其内容,依次完成目标(Goals)。
A-Maven-Project
|- pom.xml
|- src
| |- main
| | |- java
| | |- resources
| |- test
| | |- java
| | |- resources
|- target
下面是一个简单的pom.xml的例子。在该pom.xml中,modelVersion标签指明了该POM模型的版本是4.0.0。目前,4.0.0是Maven支持的唯一的版本。所以,每一个pom.xml都需要使用4.0.0版本。
groupId标签指明了组织名称或者项目名称。这个名称不应与其他组织或者项目名称相同。一个好的做法是使用点符号(.)的方式唯一指定一个项目名称,就像我们在Java语言中使用包名称(Package Name)那样,防止命名冲突。但是,groupId和包名称是完全不同的。在下面的例子中,我们指定了groupId为com.littlewaterdrop;说明这个项目是小水滴的一个项目。
artifactId标签指明了该项目的名称。这个名称不能与其他项目的名称相同。例如,在下面的例子中,我们指定了项目名称为maven-example。
version标签指明了该项目的版本号。因此,groupId:artifactId:version可以唯一确定一个组织下的一个项目的一个版本。
packaging标签指明了项目打包的目标文件的格式。在Maven项目中,最常用的两个打包文件格式是jar和war。当packaging未被定义时,缺省的目标格式是jar。Maven支持的其他格式还有:pom、maven-plugin、ejb、ear和rar。
dependencies标签定义了该项目所依赖的第三方库。当在pom.xml文件中描述了这些依赖库之后,Maven会自动地在Maven库(Maven Repository)中查找并下载这些第三方库,并自动完成项目的构建。项目之间的依赖关系(Dependency)属于Maven支持的POM关系中的一种。我们将在下面详细讲解Maven支持的POM关系(POM Relationships)。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.littlewaterdrop</groupId>
<artifactId>maven-example</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>
Maven最强的功能是处理项目之间的关系。项目关系包括:项目依赖关系(Dependency)、项目继承关系(Inheritance)、和项目聚合关系(Aggregation)。
项目依赖关系是指某一个项目的正确构建和运行依赖于另一个项目,那么,我们认为前者项目依赖于后者项目。pom.xml维护着一个项目依赖列表。当这个列表中的所有依赖关系可用时,当前项目才能正确构建和运行。Maven不仅能处理项目之间的直接关系,还能处理间接依赖关系。换句话说,Maven维护着一张项目依赖图。在构建时,Maven能自动获取所有的直接地/间接地依赖于当前项目的第三方库。因此,开发人员只需要关注于项目的直接依赖关系,Maven会自动处理所有的间接依赖。
在下面的例子中,pom.xml的依赖列表包含了一个依赖库junit,它由groupId:artifictId:version来唯一标识junit的版本。
标签scope标识了在哪个构建阶段会使用该依赖库。常见的scope的值为:compile、provided、runtime、test和system。scope标签的默认值为compile。其中,从名字可以看出compile、runtime、和test分别表示的是编译、运行、和测试三个阶段。provided表示在编译和运行时都需要这个依赖库。system与provided类似,不同之处在于这个依赖库由开发者提供;Maven不会从Maven的库中查找。
标签type标识了依赖库的类型。默认的类型是jar。
标签optional标识该依赖是否是可选的。
<project>
...
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0-M1</version>
<scope>test</scope>
<type>jar</type>
<optional>true</optional>
</dependency>
</dependencies>
...
</project>
在提供便利的同时,Maven的自动解析项目依赖的过程也会出现问题。一个常见的问题是项目依赖库的版本可能发生冲突。例如:项目A依赖于项目B和C。项目B依赖于项目D的1.0版本;而项目C依赖于项目D的2.0版本。那么,在构建项目A时,Maven会使用项目D的1.0版本还是2.0版本呢?
A --> B -> D:1.0
\
-> C -> D:2.0
为了解决这个依赖版本冲突的问题,Maven采用了软依赖(Soft Dependency)和硬依赖(Hard Dependency)的概念。软依赖是指当发生冲突时,或者指定版本不可用时,Maven可以使用其他版本来代替。硬依赖是指Maven只能使用指定的版本号。当软依赖和硬依赖发生冲突时,Maven会尽力满足硬依赖。但是,如果当两个硬依赖发生冲突,并且不存在解决方案时,Maven会报错,构建失败。
软/硬依赖是与版本号一起指定的。例如:1.0 是一个软依赖;[1.0]是一个硬依赖,表示只能用版本1.0;(, 1.0]是一个硬依赖,表示可以使用1.0版本或者早于1.0的版本;[1.2, 1.3]是一个硬依赖,表示只能使用1.2和1.3之间的版本(包含1.2和1.3版本)。
使用依赖关系,pom.xml文件还可以指明不能使用某个依赖库。被排除的依赖库可以放在<exclusions></exclusions>的标签之间。
...
<dependencies>
<dependency>
<groupId>com.littlewaterdrop</groupId>
<artifactId>maven-dependency</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>com.littlewaterdrop</groupId>
<artifactId>other-dependency</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
...
点击此处下载Maven项目模板。该项目模板使用了项目依赖关系构建项目。
Maven中的项目继承关系与面向对象程序设计的类继承关系非常相似。在项目中,开发人员可以定义一个父pom.xml文件,然后再使用子pom.xml文件,继承父pom.xml中的内容。因此,在子pom.xml文件中可以直接使用在父pom.xml中定义的属性。当父pom.xml中的内容不再适用时,开发人员还可以在子pom.xml文件中覆盖父pom.xml定义的属性。在POM继承关系下,父pom.xml被称为超级POM(Super POM)。使用继承关系能有效的减少pom.xml文件中的冗余,并且在多个项目之间共享信息。
例如,在下面的父pom.xml定义了groupId、artifactId、version和packaging。在父pom.xml中,packaging标签的值必须是pom。
在子pom.xml中可以继承父pom.xml中的内容,但是,Maven规定:artifactId、name和prerequisites是不会从父pom.xml中继承的。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.littlewaterdrop</groupId>
<artifactId>parent-artifact-id</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
</project>
在子pom.xml中可以使用<parent></parent>来引用父pom.xml。在parent标签中的groupId、artifactId和version要和父pom.xml中定义的值相同。在下面的子pom.xml中,它定义了该项目使用的artifactId,所以,该项目的唯一标识为com.littlewaterdrop:child-artifact-id:1.0。
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.littlewaterdrop</groupId>
<artifactId>parent-artifact-id</artifactId>
<version>1.0</version>
</parent>
<artifactId>child-artifact-id</artifactId>
</project>
点击此处下载使用父/子POM的项目模板。
有时,一个项目是由多个模块组成的,构建项目就是构建这些模块。因此,开发人员可以使用聚合/多模块关系来组织项目。一个典型的多模块项目的文件结构如下。在项目的根目录会有一个pom.xml,用于表示该项目包含的模块名称。在根目录下还有Module-A和Module-B两个目录,分别用于表示该项目中包含的两个模块。Module-A和Module-B目录中各自维护着一份独立的项目结构,也有着各自独立的pom.xml文件。
A-Maven-Multi-Module-Project
|- pom.xml
|- Module-A
| |- pom.xml
| |- src
| | |- main
| | | |- java
| | | |- resources
| | |- test
| | | |- java
| | | |- resources
|- Module-B
| |- pom.xml
| |- src
| | |- main
| | | |- java
| | | |- resources
| | |- test
| | | |- java
| | | |- resources
下面是一个多模块项目的主pom.xml文件(放在项目根目录下的pom.xml文件)。父pom.xml文件的packaging的值必须为pom。该文件使用<modules></modules>包含项目中的各个模块。Maven在构建多模块项目时,Maven会解析这些模块之间的依赖关系,并按照其依赖关系依次构建整个项目。所以,开发人员可以按照任意顺序组织和包含模块。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.littlewaterdrop</groupId>
<artifactId>parent-artifact-id</artifactId>
<version>2.0</version>
<packaging>pom</packaging>
<modules>
<module>module-A</module>
<module>module-B</module>
</modules>
</project>
在module-A或者module-B模块中的pom.xml文件可以使用<parent></parent>来引用和继承父pom.xml的内容。和前一小节讲述了父/子pom.xml用法类似。
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.littlewaterdrop</groupId>
<artifactId>parent-artifact-id</artifactId>
<version>1.0</version>
</parent>
<artifactId>child-artifact-id</artifactId>
</project>
点击此处下载多模块的项目模板。
POM属性是pom.xml文件中的“变量”。在定义了属性之后,开发人员可以在pom.xml文件的任何地方使用属性。例如:开发人员在pom.xml文件中定义了变量VAR,则可以使用${VAR}来引用变量的值。
例如:在下面的pom.xml中,我们定义了名为commons.logging.version的属性。所有的属性定义需要放在<properties></properties>之间,标签的名称就是该属性的名称。在依赖区(dependencies标签)中,${commons.logging.version}表示的是属性commons.logging.version的值,即1.2。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.littlewaterdrop</groupId>
<artifactId>maven-example</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<commons.logging.version>1.2</commons.logging.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons.logging.version}</version>
</dependency>
</dependencies>
</project>
Maven按照一个标准化的流程执行构造过程。这个标准化的构造过程被称为Maven构造生命周期(Build Lifecycles)。
Maven支持三个构建生命周期:default生命周期处理项目部署;clean生命周期清理项目多余的文件;site生命周期创建项目的文档。
Maven的构造生命周期是由阶段(Phase)构成的。default构建生命周期包含23个阶段;clean构建生命周期包含3个阶段;site构建生命周期包含4个阶段。在构建项目时,Maven会严格按照顺序执行各个阶段。
在每个阶段中,Maven使用目标(Goals)来表达为了达到某个目标需要执行的任务。目标是Maven可执行的最小单位,且必须与阶段(Phase)关联。Maven会按照阶段的顺序执行目标。
Default构造生命周期包含的阶段较多,她们依次是:
开发人员可以使用如下命令执行default构造生命周期。
mvn
clean构造生命周期包含:
开发人员可以使用如下命令执行Clean构造生命周期。
mvn clean
site构造生命周期包含:
开发人员可以使用如下命令执行site构造生命周期。
mvn site
目标(Goals)是构建生命周期的最小单位,一个目标代表着一项具体的任务。例如:拷贝文件、编译源代码、打包等都是一个个具体的目标。Maven提供了一些随Maven发布的目标;开发人员也可以根据自身需求开发新的目标。
每个目标都绑定于一个阶段之中。换句话说,目标只能在所绑定的阶段中执行。Maven提供的目标有:
构建生命周期(Lifecycle) | 阶段(phase) | 插件:目标(plugin:goal) |
---|---|---|
default | process-resources | resources:resources |
default | compile | compiler:compile |
default | process-test-resources | resources:testResources |
default | test-compile | compiler:testCompile |
default | test | surefire:test |
default | package | jar:jar, war:war |
default | install | install:install |
default | deploy | deploy:deploy |
clean | clean | clean:clean |
site | site | site:site |
site | site-deploy | site:deploy |
开发人员可以单独运行一个目标。例如:开发人员可以使用如下命令编译项目。
mvn compile:compile
Maven插件(Maven Plugins)是一组目标的集合。一个插件可以包含运行在不同阶段的目标。
例如,在如下的插件中定义了两个目标integration-test和verify。她们分别被绑定在integration-test阶段和verify阶段,用于运行集成测试和检查集成测试结果。
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven.failsafe.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
开发人员可以使用如下命令单独运行一个目标(Goal),而不需要运行整个阶段(Phase)。
mvn PLUGIN:GOAL
Maven之所以能自动查找并获取第三方代码库(3rd-Party Libraries),是因为Maven维护着一个庞大的Maven库(Maven Repository)。在这个库中,Maven存放了大量的第三方代码库。这些第三方代码库在Maven库中发布他们的.jar文件、插件(plugins)、或者其他的项目发布的文件(Project Artifact)。
Maven库分为三个种类,它们分别是本地库(Local Repository),远程库(Remote Repository),和中心库(Central Repository)。
本地库是在本地服务器上的一个目录,它保存着项目所依赖的所有文件。当第一次运行Maven时,Maven会自动地从其他库中查找依赖的文件,并下载到本地库中。所以,当Maven再次运行时,Maven能够直接从本地库中读取依赖文件,而无需再从远程库或者中心库读取。
远程库是由开发人员维护的依赖库。在远程库中,开发人员可以放置一些公司或者组织内部的项目文件,或者经特殊处理过的项目文件。这个远程库仅提供内部开发人员使用。
中心库是由Maven社区提供和维护的项目文件库。它包含了大量的,开发人员经常使用的第三方代码库。Maven中心库里的所有项目都可以在Maven Central Repository Search查找。
当Maven运行时,查找项目依赖库的顺序为:
开发人员可以按照上述Maven项目结构和POM的工作原理来手动创建一个Maven项目。本文还提供了三个Maven项目的模板,方便读者快速创建项目(她们只包含了文件结构和空的源代码文件)。Maven项目模板、Maven父/子继承关系模板和Maven多模块项目模板。
与此同时,Maven工具也提供了创建新项目的工具。开发人员可以按照如下的命令,并填入相应的groupId、artifactId即可生成一个新项目。
mvn archetype:generate \
-DgroupId=com.littlewaterdrop \
-DartifactId=maven-example \
-DinteractiveMode=false
在新项目创建完成后,可以运行下面的命令构建该项目。
cd maven-example
mvn clean package
本章介绍了Maven工具的基本概念和项目的结构。POM是Maven工具的核心;Maven支持三种项目关系:依赖、继承、和多模块。根据这三种关系,Maven能自动的处理项目之间的依赖,帮助开发人员简化项目构建过程。
注册用户登陆后可留言