【Maven系列】POM官网权威详解
源自专栏《Gradle ScalaTest markdown idea Git中文实用教程目录?》
文章目录
一、POM是什么?
POM代表“项目对象模型”(Project Object Model)。它是一个Maven项目的XML表示,保存在名为pom.xml的文件中。在Maven的使用者中,谈论一个项目是在哲学意义上谈论,不仅仅是一堆包含代码的文件集合。
一个项目包含
-
配置文件,
-
涉及的开发人员及其角色,
-
缺陷跟踪系统,
-
组织和许可证,
-
项目所在位置的URL,
-
项目的依赖关系,
-
以及所有其他组成部分,以使代码活跃起来。
它是关于项目的所有事情的一站式商店。实际上,在Maven世界中,一个项目甚至不需要包含任何代码,只需要一个pom.xml文件。
1. 快速概述
以下是POM的project元素下直接的元素列表。注意,modelVersion包含4.0.0。这目前是唯一支持的POM版本,也是必需的。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 基本信息 -->
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<dependencies>...</dependencies>
<parent>...</parent>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>
<!-- 构建设置 -->
<build>...</build>
<reporting>...</reporting>
<!-- 更多项目信息 -->
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>
<!-- 环境设置 -->
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement>
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>
二、基本信息
POM包含关于项目的所有必要信息,以及在构建过程中要使用的插件的配置。它是“谁”、“什么”和“在哪里”的声明化体现,而构建生命周期是“何时”和“如何”。这并不是说POM不能影响生命周期的流程 - 它可以。例如,通过配置maven-antrun-plugin,可以在POM中嵌入Apache Ant任务。然而,它最终是一个声明,与之相对的是,build.xml文件告诉Ant在运行时要做什么(程序性的),而POM陈述了它的配置(声明性的)。如果某个外部力量导致生命周期跳过Ant插件的执行,这并不会阻止正在执行的插件发挥其魔力。这与build.xml文件不同,其中任务几乎总是依赖于它之前执行的行。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.codehaus.mojo</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
</project>
2.1 Maven坐标
上述定义的POM是Maven允许的最基本的。groupId:artifactId:version是所有必需字段(尽管,如果从父项继承,groupId和version不需要显式定义 - 更多继承的内容稍后)。这三个字段就像地址和时间戳一样。这标记了仓库中的特定位置,充当Maven项目的坐标系统:
- groupId:这通常在组织或项目中是唯一的。例如,所有核心的Maven构件(应该)位于groupId org.apache.maven下。组ID不一定使用点表示法,例如junit项目。请注意,点表示法的groupId不必与项目包含的包结构对应。然而,遵循这种做法是一个好习惯。当存储在仓库中时,组就像Java包结构在操作系统中的作用一样。点被OS特定的目录分隔符(如Unix中的’/')替换,形成从基本仓库的相对目录结构。在给定的示例中,org.codehaus.mojo组位于目录$M2_REPO/org/codehaus/mojo下。
- artifactId:artifactId通常是项目所知的名称。尽管groupId很重要,但组内的人很少在讨论中提到groupId(它们通常都是相同的ID,例如MojoHaus项目groupId: org.codehaus.mojo)。它与groupId一起创建一个键,将该项目与世界上的每个其他项目分开(至少应该是如此 😃)。与groupId一起,artifactId完全定义了仓库中的构件存放位置。在上述项目的情况下,my-project位于$M2_REPO/org/codehaus/mojo/my-project下。
- version:这是命名谜题的最后一块。groupId:artifactId表示一个项目,但它们无法区分我们所说的该项目的哪个具体版本。我们是要2018年的junit:junit(版本4.12),还是2007年的junit:junit(版本3.8.2)?简而言之:代码会发生变化,这些变化应该被版本化,这个元素保持这些版本保持一致。它还用于在一个构件的仓库中将版本与其他版本区分开来。my-project版本1.0的文件位于目录结构$M2_REPO/org/codehaus/mojo/my-project/1.0中。
上述给出的三个元素指向项目的特定版本,让Maven知道我们正在处理谁,以及在其软件生命周期中我们想要它们。
2.2 打包
现在我们已经有了groupId:artifactId:version的地址结构,还有一个标准标签可以让我们完整地了解项目:即项目的打包方式。在我们的例子中,上面定义的org.codehaus.mojo:my-project:1.0示例POM将被打包为一个jar文件。如果我们声明不同的打包方式,我们可以将其制作成一个war文件:
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<packaging>war</packaging>
...
</project>
当没有声明打包方式时,Maven默认打包方式是jar。有效的类型是组件角色org.apache.maven.lifecycle.mapping.LifecycleMapping的Plexus角色提示(阅读Plexus了解角色和角色提示的更多解释)。
当前的核心打包值有:pom、jar、maven-plugin、ejb、war、ear、rar。
这些定义了在特定的包结构上每个对应构建生命周期阶段上执行的默认目标列表:详细信息请参阅默认生命周期参考的插件绑定。
2.3 POM关系
Maven的一个强大方面是它对项目关系的处理:这包括依赖关系(以及传递依赖关系)、继承和聚合(多模块项目)。
2.3.1 依赖项
POM的基石是其依赖列表。大多数项目依赖于其他项目以正确构建和运行。如果Maven为您管理此列表,您就获得了很多好处。Maven在编译时下载和链接依赖项,以及在需要它们的其他目标上。作为额外的好处,Maven还会引入这些依赖项的依赖项(传递依赖关系),使您的列表可以专注于项目所需的依赖项。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<type>jar</type>
<scope>test</scope>
<optional>true</optional>
</dependency>
...
</dependencies>
...
</project>
groupId、artifactId、version
您经常会看到这些元素。这三个元素用于计算特定项目的Maven坐标,将其标记为此项目的依赖项。这种计算的目的是选择一个与所有依赖声明匹配的版本(由于传递依赖关系,可能会有多个相同构件的依赖声明)。这些值应该是:
- groupId、artifactId:直接对应依赖项的相应坐标,
- version:一个依赖版本要求规范,用于计算依赖的有效版本。
因为依赖项由Maven坐标描述,您可能会想:“这意味着我的项目只能依赖于Maven构件!”答案是:“当然,但这是一件好事。”这迫使您仅依赖于Maven可以管理的依赖项。
不幸的是,有时项目无法从中央Maven仓库下载。例如,一个项目可能依赖于一个具有闭源许可证的jar,这阻止它出现在中央仓库中。有三种方法可以处理这种情况。
-
使用install插件在本地安装依赖项。这是推荐的最简单的方法。例如:
mvn install:install-file -Dfile=non-maven-proj.jar -DgroupId=some.group -DartifactId=non-maven-proj -Dversion=1 -Dpackaging=jar
注意,仍然需要一个地址,只是这一次您使用命令行,install插件将为您创建一个具有给定地址的POM。
-
创建您自己的仓库,并将其部署在那里。这是一种公司在内部网络中经常使用的方法,需要能够让每个人保持同步。有一个名为deploy:deploy-file的Maven目标,它类似于install:install-file目标(阅读插件目标页面以获取更多信息)。
-
将依赖范围设置为system,并定义一个systemPath。然而,不建议这样做,但这将引出以下元素的解释:
- classifier:分类器区分了从同一POM构建而成但内容不同的构件。这是一些可选的和任意的字符串,如果存在,则会附加到版本号后的构件名称。
作为这个元素的动机,例如考虑一个项目,它提供一个针对Java 11的构件,同时也提供一个仍支持Java 1.8的构件。第一个构件可以使用分类器jdk11,第二个构件可以使用jdk8,这样客户端可以选择使用哪一个。
分类器的另一个常见用例是将辅助构件附加到项目的主构件上。如果浏览Maven中央仓库,您会注意到分类器sources和javadoc用于将项目源代码和API文档与打包的类文件一起部署。
-
type:对应所选依赖项的类型。默认为jar。虽然它通常表示依赖项的文件名的扩展名,但并非总是如此:类型可以映射到不同的扩展名和分类器。类型通常对应于使用的打包,尽管这也并非总是如此。一些示例是jar、ejb-client和test-jar:请查看默认构件处理程序以获取列表。新类型可以由将扩展设置为true的插件定义,因此这不是一个完整的列表。
-
scope:此元素指的是当前任务的类路径(编译和运行时,测试等),以及如何限制依赖关系的传递。有五个可用的范围:
- compile - 默认范围,如果未指定范围,则使用此范围。编译依赖项在所有类路径中都可用。此外,这些依赖项传递给依赖项目。
- provided - 这与compile类似,但表示您期望JDK或容器在运行时提供它。它仅在编译和测试类路径上可用,不是传递性的。
- runtime - 此范围表示依赖项不需要用于编译,但需要用于执行。它在运行时和测试类路径上,但不在编译类路径上。
- test - 此范围表示依赖项不需要用于应用程序的正常使用,仅在测试编译和执行阶段中可用。它不是传递性的。
- system - 此范围与provided类似,但必须显式提供包含它的JAR。该构件始终可用,并且不会在仓库中查找。
-
systemPath:仅在依赖范围为system时使用。否则,如果设置了此元素,构建将失败。路径必须是绝对的,因此建议使用属性指定机器特定的路径(后面会更多介绍属性),例如${java.home}/lib。由于假定系统范围的依赖项是预先安装的,因此Maven不会检查项目的仓库,而是检查文件是否存在。如果不存在,Maven将导致构建失败,并建议您手动下载和安装它。
-
optional:当此项目本身是一个依赖项时,标记一个依赖项为可选。例如,假设项目A依赖于项目B来编译可能在运行时不会使用的代码部分,那么我们可能并不需要项目B用于所有项目。因此,如果项目X将项目A添加为自己的依赖项,那么Maven根本不需要安装项目B。从象征意义上讲,如果=>表示一个必需依赖项,则–>表示可选依赖项,尽管A=>B可能是构建A时的情况,但在构建X时,X=>A–>B就是情况。在最简短的术语中,可选让其他项目知道,当您使用此项目时,您不需要此依赖项才能正确工作。
2.3.1.1 依赖管理
依赖关系可以在dependencyManagement部分中进行管理,以影响那些没有完全限定的依赖关系的解析,或者强制使用特定传递依赖版本。有关更多信息,请参阅《依赖机制简介》。
2.3.1.2 依赖版本要求规范
依赖版本元素定义了版本要求,用于计算依赖版本。软要求可以被找到在依赖图中的同一构件的不同版本替代。硬要求规定了特定版本或版本,并覆盖软要求。如果没有一个版本满足该构件的所有硬要求,构建就会失败。
版本要求具有以下语法:
- 1.0:1.0的软要求。如果在依赖树中没有出现其他版本,则使用1.0。
- [1.0]:对1.0的硬要求。使用1.0,仅使用1.0。
- (,1.0]:任何<= 1.0版本的硬要求。
- [1.2,1.3]:1.2到1.3之间的任何版本的硬要求。
- [1.0,2.0):1.0 <= x < 2.0;1.0包括在内,2.0不包括在内的任何版本的硬要求。
- [1.5,):大于或等于1.5的任何版本的硬要求。
- (,1.0],[1.2,):小于或等于1.0,大于或等于1.2的任何版本的硬要求,但不是1.1。多个要求用逗号分隔。
- (,1.1),(1.1,):除1.1之外的任何版本的硬要求;例如因为1.1存在关键漏洞。
Maven会选择满足项目上所有依赖项的硬要求的最高版本。如果没有版本满足所有硬要求,构建将失败。
2.3.1.3 版本顺序规范
如果版本字符串在语法上正确的语义版本1.0.0版本号中,那么在几乎所有情况下,版本比较遵循该规范中概述的优先规则。这些版本通常是常见的字母数字ASCII字符串,如2.15.2-alpha。更准确地说,如果要比较的两个版本号匹配语义版本规范中的BNF语法中的“valid semver”产生物,则这是正确的。Maven不考虑该规范暗示的任何语义。
重要提示:这仅适用于语义版本1.0.0。Maven版本顺序算法与语义版本2.0.0不兼容。特别地,Maven不特殊处理加号,也不考虑构建标识符。
当版本字符串不遵循语义版本规范时,需要一组更复杂的规则。Maven坐标在句点(‘.’)、连字符(‘-’)、下划线(‘_’)之间的令牌中分割,并在数字和字符之间的转换之间。分隔符被记录下来,并会影响顺序。数字和字符之间的转换等同于连字符。空令牌被替换为“0”。这会产生一系列版本号(数字令牌)和版本限定符(非数字令牌),带有“.”或“-”前缀。版本应以数字开头。
2.3.1.4 分割和替换示例:
1-1.foo-bar1baz-.1 -> 1-1.foo-bar-1-baz-0.1
然后,从版本的末尾开始,尾随的“null”值(0、“”、“final”、“ga”)被修剪。这个过程在每个剩余的连字符从结尾到开头重复。
2.3.1.5 修剪示例:
1.0.0 -> 1
1.ga -> 1
1.final -> 1
1.0 -> 1
- -> 1
1- -> 1
1.0.0-foo.0.0 -> 1-foo
1.0.0-0.0.0 -> 1
版本顺序是这一系列带前缀令牌的词汇顺序,较短的词会用足够多的与较长词长度相匹配的“null”值填充。填充的“null”值取决于另一个版本的前缀:对于’.‘,填充为0,对于’-',填充为""。带前缀的令牌顺序是:
- 如果前缀相同,则比较令牌:
- 数字令牌具有自然顺序。
- 非数字令牌(“限定符”)按字母顺序排列,除了以下令牌,它们在此顺序中首先出现:
- “alpha” < “beta” < “milestone” < “rc” = “cr” < “snapshot” < “” = “final” = “ga” < “sp”
“alpha”,“beta”和“milestone”限定符可以在后面直接跟着一个数字时分别缩短为“a”,“b”和“m”。
其他情况下,“.qualifier” = “-qualifier” < “-number” < “.number”
alpha = a <<> = b <<> = m <<> = cr <<> ‘<<<>>’ = final = ga = release < sp
鼓励遵循semver规则,有些限定符是不鼓励使用的:
- 更喜欢’alpha’,‘beta’和’milestone’限定符,而不是’ea’和’preview’。
- 更喜欢’1.0.0-RC1’而不是’1.0.0.RC1’。
- 不鼓励使用’CR’限定符。请改用’RC’。
- 不鼓励使用’final’,'ga’和’release’限定符。请改用无限定符。
- 不鼓励使用’SP’限定符。请增加修订版本号。
2.3.1.6 最终结果示例:
- “1” < “1.1”(数字填充)
- “1-snapshot” < “1” < “1-sp”(限定符填充)
- “1-foo2” < “1-foo10”(正确地自动“切换”到数字顺序)
- “1.foo” = “1-foo” < “1-1” = “1.1”
- “1.ga” = “1-ga” = “1-0” = “1.0” = “1”(去掉尾随的“null”值)
- “1-sp” > “1-ga”
- “1-sp.1” > “1-ga.1”
- “1-sp-1” > “1-ga-1”
- “1-a1” = “1-alpha-1”
**注意:**与某些设计文档中所述的相反,在版本顺序中,快照与发布版本或任何其他限定符没有区别。
2.3.2 继承
Maven为构建管理带来的一个强大功能是项目继承的概念。虽然在构建系统中,如Ant中可以模拟继承,但Maven在项目对象模型中明确地展现了项目继承。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.codehaus.mojo</groupId>
<artifactId>my-parent</artifactId>
<version>2.0</version>
<packaging>pom</packaging>
</project>
对于父项目和聚合(多模块)项目,打包类型必须是pom。这些类型定义了绑定到一组生命周期阶段的目标。例如,如果打包是jar,那么打包阶段将执行jar:jar目标。现在,我们可以向父POM添加值,这些值将被其子项目继承。
父POM中的大多数元素都会被其子项目继承,包括:
- groupId
- version
- description
- url
- inceptionYear
- organization
- licenses
- developers
- contributors
- mailingLists
- scm
- issueManagement
- ciManagement
- properties
- dependencyManagement
- dependencies
- repositories
- pluginRepositories
- build
- 具有匹配id的插件执行
- 插件配置
- 等等
**注意:**不继承的重要元素包括:
- artifactId
- name
- prerequisites
- profiles(但父POM的活动配置文件的影响是会继承的)
<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 https://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>
注意relativePath元素。它不是必需的,但可以用作Maven的一个标志,指示首先在给定路径中搜索此项目的父项,然后在本地和远程仓库中搜索。
要查看继承的实际效果,只需查看ASF或Maven父POM的内容。
详细的继承规则在Maven模型构建器中概述。默认情况下,所有URL在继承时都会被转换。其他的只是按原样继承。对于插件配置,您可以使用插件中概述的combine.children或combine.self属性来覆盖继承行为。
2.3.2.1 超级POM
类似于面向对象编程中对象的继承,扩展父POM的POM从父项目继承某些值。此外,正如Java对象最终继承自java.lang.Object一样,所有项目对象模型都继承自基本的超级POM。以下代码段是Maven 3.5.4的超级POM。
<project>
<modelVersion>4.0.0</modelVersion>
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name