Maven使用教程

Maven介绍

Maven 翻译为”专家”、”内行”,是 Apache 下的一个纯 Java 开发的开源项目。基于项目对象模型(缩写:POM)概念,Maven利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。

Maven 是一个项目管理工具,可以对 Java 项目进行构建、依赖管理。

Maven 也可被用于构建和管理各种项目,例如 C#,Ruby,Scala 和其他语言编写的项目。Maven 曾是 Jakarta 项目的子项目,现为由 Apache 软件基金会主持的独立 Apache 项目。

Maven POM

POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个XML文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖,等等。

执行任务或目标时,Maven 会在当前目录中查找 POM。它读取 POM,获取所需的配置信息,然后执行目标。

POM 中可以指定以下配置:

  • 项目依赖

  • 插件

  • 执行目标

  • 项目构建 profile

  • 项目版本

  • 项目开发者列表

  • 相关邮件列表信息

所有 POM 文件都需要 project 元素和三个必需字段:groupId,artifactId,version。

节点 描述
modelVersion 模型版本
groupId 这是工程组的标识。它在一个组织或者项目中通常是唯一的
artifactId 这是工程的标识。它通常是工程的名称
version 这是工程的版本号。在 artifact 的仓库中,它用来区分不同的版本

父(Super)POM

在 Java 中,所有的类都继承自 Object 超类,而在 maven 中也存在 The Super POM,所有 maven 工程的pom 文件都继承自该文件,那这个 The Super POM 中设置了什么呢?

下面展示的 maven 3.5.4 版本的 The Super POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<project>
<modelVersion>4.0.0</modelVersion>

<repositories>
<repository>...</repository>
</repositories>
<pluginRepositories>
<pluginRepository>...</pluginRepository>
</pluginRepositories>

<build>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
<testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources</directory>
</testResource>
</testResources>
<pluginManagement>
<!-- NOTE: These plugins will be removed from future versions of the super POM -->
<!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
<plugins>
...
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
</plugin>
...
</plugins>
</pluginManagement>
</build>

<reporting>
<outputDirectory>${project.build.directory}/site</outputDirectory>
</reporting>

<profiles>...</profiles>

</project>

我对上面的 pom 文件进行了删减,仅展示了部分内容,它们可以概括为以下 3 个部分:

  • 默认的 repository

maven 默认使用官方提供的 central repository,这也是你可以开箱即用的原因,而在安装阶段配置国内的镜像源就是为了替换默认的设置。

  • 标准的目录骨架

build 标签中,可以看到如 sourceDirectorytestSourceDirectoryoutputDirectory 等标签,这些标签为我们设置了默认的 maven 目录骨架,明确了源代码目录、测试目录、输出目录等各种目录,使得我们在接触一个任何一个 maven 工程时都可以快速找到相对应的代码入口。

这也提示我们,其实我们可以自定义 maven 目录结构,但完全不推荐这么做。

  • 默认的插件版本

在 The Super POM 中还定义了多个默认插件的版本,使得你省去了配置插件的工作,当然你也可以自己设置插件的版本号。

Maven 使用 effective pom(Super pom 加上工程自己的配置)来执行相关的目标,它帮助开发者在 pom.xml 中做尽可能少的配置,当然这些配置可以被重写。

使用以下命令来查看 Super POM 默认配置:

1
mvn help:effective-pom

Effective POM 的结果是经过继承、插值之后,使配置生效。

常用POM标签解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<?xml version="1.0" encoding="UTF-8"?>
<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">

<!--声明项目描述符遵循哪一个POM模型版本。模型本身的版本很少改变,虽然如此,但它仍然是必不可少的,这是为了当Maven引入了新的特性或者其他模型变更的时候,确保稳定性。 -->
<modelVersion>4.0.0</modelVersion>

<!--项目的全球唯一标识符,通常使用全限定的包名区分该项目和其他项目。并且构建时生成的路径也是由此生成, 如com.mycompany.app生成的相对路径为:/com/mycompany/app -->
<groupId>org.example</groupId>
<!-- 构件的标识符,它和group ID一起唯一标识一个构件。换句话说,你不能有两个不同的项目拥有同样的artifact ID和groupID;在某个
特定的group ID下,artifact ID也必须是唯一的。构件是项目产生的或使用的一个东西,Maven为项目产生的构件包括:JARs,源 码,二进制发布和WARs等。 -->
<artifactId>maven_test</artifactId>
<!--项目产生的构件类型,例如jar、war、ear、pom。插件可以创建他们自己的构件类型,所以前面列的不是全部构件类型 -->
<packaging>jar</packaging>
<!--项目当前版本,格式为:主版本.次版本.增量版本-限定版本号 -->
<version>1.0-SNAPSHOT</version>

<!--项目的名称, Maven产生的文档用 -->
<name>maven-test</name>

<!--&lt;!&ndash;描述了这个项目构建环境中的前提条件。 &ndash;&gt;
<prerequisites>
&lt;!&ndash;构建该项目或使用该插件所需要的Maven的最低版本 &ndash;&gt;
<maven />
</prerequisites>-->

<!--以值替代名称,Properties可以在整个POM中使用,也可以作为触发条件(见settings.xml配置文件里activation元素的说明)。格式是<name>value</name>。 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<log4j2-version>2.13.3</log4j2-version>
<camel.version>3.8.0</camel.version>
<camel-main.version>3.8.0</camel-main.version>
</properties>

<!-- 继承自该项目的所有子项目的默认依赖信息。这部分的依赖信息不会被立即解析,而是当子项目声明一个依赖(必须描述group ID和 artifact
ID信息),如果group ID和artifact ID以外的一些信息没有描述,则通过group ID和artifact ID 匹配到这里的依赖,并使用这里的依赖信息。 -->
<dependencyManagement>
<!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。要获取更多信息,请看项目依赖机制。 -->
<dependencies>
<!-- Camel BOM -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-bom</artifactId>
<version>3.8.0</version>
<!--依赖范围。在项目发布过程中,帮助决定哪些构件被包括进来。欲知详情请参考依赖机制。 - compile :默认范围,用于编译 - provided:类似于编译,但支持你期待jdk或者容器提供,类似于classpath
- runtime: 在执行时需要使用 - test: 用于test任务时使用 - system: 需要外在提供相应的元素。通过systemPath来取得
- systemPath: 仅用于范围为system。提供相应的路径 - optional: 当项目自身被依赖时,标注依赖是否传递。用于连续依赖时使用
- import: 这个作用域只支持<dependencyManagement>部分中的pom类型的依赖关系,它表示该依赖关系要被指定的POM的<dependencyManagement>部分中的有效依赖列表所取代。
它表示该依赖关系将被指定的 POM 的 <dependencyManagement> 部分中的有效依赖列表所替换。
由于它们被替换,所以具有导入范围的依赖关系实际上并不参与限制依赖关系的转义。-->
<scope>import</scope>
<!-- 依赖类型,默认类型是jar。它通常表示依赖的文件的扩展名,但也有例外。一个类型可以被映射成另外一个扩展名或分类器。类型经常和使用的打包方式对应,
尽管这也有例外。一些类型的例子:jar,war,ejb-client和test-jar。如果设置extensions为 true,就可以在 plugin里定义新的类型。所以前面的类型的例子不完整。 -->
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>

<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-main</artifactId>
<version>${camel-main.version}</version>
</dependency>

<!-- logging -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>runtime</scope>
<version>${log4j2-version}</version>
</dependency>

<!-- testing -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<!--构建项目需要的信息 -->
<build>
<defaultGoal>install</defaultGoal>

<!--使用的插件列表 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<!--作为DOM对象的配置 -->
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

<!-- Allows the example to be run via 'mvn camel:run' -->
<plugin>
<groupId>org.apache.camel</groupId>
<artifactId>camel-maven-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<logClasspath>true</logClasspath>
<mainClass>org.example.MainApp</mainClass>
</configuration>
</plugin>

</plugins>
</build>

<!--发现依赖和扩展的远程仓库列表。 -->
<repositories>
<!--包含需要连接到远程仓库的信息 -->
<repository>
<!--远程仓库唯一标识符。可以用来匹配在settings.xml文件里配置的远程仓库 -->
<id>maven-ali</id>
<!--远程仓库名称 -->
<name>aliyun-repo</name>
<!--远程仓库URL,按protocol://hostname/path形式 -->
<url>https://maven.aliyun.com/repository/public</url>
<!--如何处理远程仓库里发布版本的下载 -->
<releases>
<!--true或者false表示该仓库是否为下载某种类型构件(发布版,快照版)开启。 -->
<enabled>true</enabled>
</releases>
<!-- 如何处理远程仓库里快照版本的下载。有了releases和snapshots这两组配置,POM就可以在每个单独的仓库中,为每种类型的构件采取不同的
策略。例如,可能有人会决定只为开发目的开启对快照版本下载的支持。参见repositories/repository/releases元素 -->
<snapshots>
<enabled>true</enabled>
<!--该元素指定更新发生的频率。Maven会比较本地POM和远程POM的时间戳。这里的选项是:always(一直),daily(默认,每日),interval:X(这里X是以分钟为单位的时间间隔),或者never(从不)。 -->
<updatePolicy>always</updatePolicy>
<!--当Maven验证构件校验文件失败时该怎么做:ignore(忽略),fail(失败),或者warn(警告)。 -->
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>

<!--项目分发信息,在执行mvn deploy后表示要发布的位置。有了这些信息就可以把网站部署到远程服务器或者把构件部署到远程仓库。 -->
<distributionManagement>
<!--部署项目产生的构件到远程仓库需要的信息 -->
<repository>
<id>roger-public</id>
<name>Roger-maven3</name>
<url>http://127.0.0.1:8081/repository/maven-releases/</url>
</repository>
<!--构件的快照部署到哪里?如果没有配置该元素,默认部署到repository元素配置的仓库,参见distributionManagement/repository元素 -->
<snapshotRepository>
<id>roger-public</id>
<name>Roger-maven3 Snapshot Repository</name>
<url>http://127.0.0.1:8081/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>


</project>

Maven生命周期

Maven 构建生命周期定义了一个项目构建跟发布的过程。

一个典型的 Maven 构建(build)生命周期是由以下几个阶段的序列组成的:

img

阶段 处理 描述
validate 验证项目 验证项目是否正确且所有必须信息是可用的
compile 执行编译 源代码编译在此阶段完成
test 测试 使用适当的单元测试框架(例如JUnit)运行测试。
package 打包 创建JAR/WAR包如在 pom.xml 中定义提及的包
verify 检查 对集成测试的结果进行检查,以保证质量达标
install 安装 安装打包的项目到本地仓库,以供其他项目使用
deploy 部署 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程

maven 定义了三种构建**生命周期(Build Lifecycle),分别是 **cleandefaultsite,它们分别用于不同的任务:

  • clean 生命周期:处理工程构建前的清理工作;
  • default 生命周期:处理工程的编译、打包、部署;
  • site 生命周期:生成工程的文档。

每一种构建生命周期都由多个构建阶段(Build Phase)组成,如 clean 生命周期就由 pre-clean、clean、post-clean 三个构建阶段组成,而 default 声明周期则有 23 个构建阶段,详细内容可查看官方文档

maven 插件

虽然 maven 定义了构建生命周期和构建阶段,并指明了每个阶段的任务目标,但这些目标都是由 maven 插件(plugin)实现的,可以将构建阶段类比为一个 Java 接口,而将插件类比为一个 Java 实现类。

maven 插件有一个很重要的概念:goal,你可以直译为“目标”,但整个知识库仍直接使用英语原文。一个插件可以有多个 goal,一个 goal 对应一个功能,比如 maven-compiler-plugin 插件,就有两个 goal: compiletestCompile ,前者表示编译工程代码,后者表示编译工程测试代码。

内置生命周期绑定

“生命周期绑定”指的是将“生命周期的构建阶段”与“plugin:goal”绑定,而“内置(build-in)”指的是这是 maven 默认的。需要特别指出的是:不是所有构建阶段都(默认)绑定了 plugin:goal

下面列出了几个常见的生命周期绑定,详细内容请查阅官方文档

iXmrpw

构建阶段的有序性

使用 mvn compile 命令编译代码时的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.example:first-maven-project >-------------------
[INFO] Building first-maven-project 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ first-maven-project ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ first-maven-project ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/liming/Program/maven-tutorial/build-first-maven-project/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.006 s
[INFO] Finished at: 2020-10-31T09:40:56+08:00
[INFO] ------------------------------------------------------------------------

首先, mvn compile 命令表示执行(default 生命周期的)compile 阶段,而 compile 阶段默认绑定了 maven-compiler-plugin:compile ,所以 mvn compile 命令就相当于执行 mvn compiler:compile 命令,因此在第 12 行你看到了对应的提示。

那为什么在执行 maven-compiler-plugin:compile 前会先执行 maven-resources-plugin:resources 呢?这就与“构建阶段的有序性”有关了。

构建阶段的有序性”是指在同一种生命周期内,不同构建阶段的执行是有序的,执行某个阶段前,必须先执行完该阶段的所有前置阶段。如 compile 阶段属于 default 生命周期,而 process-resources 阶段在 compile 阶段之前,且默认绑定了 maven-resources-plugin:resources ,所以你看到第 8 行的提示。

前面说过,maven 并未对所有阶段都绑定 plugin:goal,所以即使构建阶段有序,有些阶段也可能并未真正执行。

至此,构建第一个 maven 工程时留下的疑惑就只剩下第 9 行和第 14 行的警告了,这个问题将在后文进行解答。