前言
Maven — 自动化构建工具(build automation tool),它提供了以下功能:
- 强大的依赖管理
- 完善的生命周期管理(lifecycle & phase)
- 以插件为核心(plugin & goal)
- 支持多种打包方式
- 支持子工程(模块)
- 可重现构建(Reproducible builds)
Maven 广泛用于 Java 项目的自动化构建,可以跟其他工具(如 Jenkins)一起实现 CI & CD。
注:本文不会介绍 Maven 的下载、安装、配置,只介绍 Maven 在项目中如何使用。
目录结构
如果我们打算使用 Maven 构建我们的 Java 项目,我们 Java 项目的目录结构必须如下:
详细说明:
目录 | 用途 |
---|---|
project home | 项目的根目录,包含了 pom.xml 文件、其他子目录 |
src/main/java | 包含 Java 源代码 |
src/main/resources | 包含资源文件 |
src/test/java | 包含测试用的 Java 源代码 |
src/test/resources | 包含测试用的资源文件 |
target | 包含最终构建出来的产物,比如产生的包、字节码文件、资源文件、pom.xml |
当然还可以包含其他文件,比如我们可以把项目的说明文件 README.md
、数据库初始化语句 init.sql
放在 project home
下。
Maven 构建 Java 项目的过程:
- 读取
src/main/java
目录,获取 Java 代码编译; - 读取
src/main/resources
目录,获取资源文件; - 最后根据
pom.xml
配置的打包方式打包,把包放在target
目录下,并推送到 本地仓库 & 远程仓库。
注:以上只是 Maven 大概的构建过程,后文讲述细节。
POM
POM : Project Object Model
,这是 Maven 用来表示整个项目信息的一个模型,用 xml
表示,存放在项目根目录下的 pom.xml
。
pom.xml
是 Maven 的配置文件,项目的所有配置信息都放在这里。
当 Maven 构建项目时,会找到项目根目录下的 pom.xml
,读取其中的配置信息,完成整个构建过程。
pom.xml
配置了以下信息:
- 项目的
name, description, owner, developer, groupId, artifactId, version
- 打包方式(
pom, jar, war, ear
) - 依赖 (
dependency
) - 插件(
plugin
) - lifecycle
- build directory -
target
(默认值) - source directory -
src/main/java
(默认值) - test source directory -
src/test./java
(默认值)
如果项目比较复杂,我们可以把整个项目拆分成 子工程(也叫 模块);
这样,父工程有一个 pom.xml
(打包方式为 pom
),每个子工程也有一个 pom.xml
。
详情参看:https://maven.apache.org/guides/introduction/introduction-to-the-pom.html
打包方式 - packaging
在 pom.xml
定义打包方式:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
<packaging>pom</packaging> <!-- 定义打包方式 -->
</project>
打包方式:
方式 | 示例 | 说明 |
---|---|---|
jar | <packaging>jar</packaging> | 如果没有指定打包方式,默认为 jar |
pom | <packaging>pom</packaging> | 打包得到的项目只有 pom.xml 文件(一般在父子项目中,父项目的打包方式为 pom ) |
war | <packaging>war</packaging> | 可以部署到服务器运行 |
自定义的打包方式 | <packaging>自定义</packaging> | 可以自定义打包方式 |
依赖机制
依赖管理是 Maven 核心功能。那么,Maven 是怎么管理依赖呢?(比如,同一个依赖,引入了不同版本,最终使用哪个版本?)
Maven 依赖机制实现了以下功能:
- Dependency mediation:依赖仲裁
- Dependency management:依赖管理
- Dependency scope:依赖范围
- Excluded dependencies:排除依赖
- Optional dependencies:可选依赖
Maven 依赖机制,详情参看:https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html。
直接依赖 & 传递依赖
A
└── B
└── C
└── D 2.0
A
是我们的项目, 其中 B
是 A
的直接依赖,C
、D
是 A
的传递依赖(transitive dependency)。
Dependency mediation - 依赖仲裁
A
├── B
│ └── C
│ └── D 2.0
└── E
└── D 1.0
如上图所示,项目 A 的依赖关系形成了一棵树;
这棵树有两个分支,分别依赖了不同版本的 D:
A -> B -> C -> D 2.0
A -> E -> D 1.0
Maven 根据 neareast definition
确定使用哪个依赖,这个过程就叫 依赖仲裁。
neareast definition - 最近定义
类似距离根节点的最短路径。
- 如果在依赖树中,同一个依赖出现了不同的版本,选择 距离根节点最近的那个依赖 的版本。
- 如果距离相同,选择 先出现的依赖 的版本。
这里 D 1.0
距离 A
最近,所以使用 D 1.0
版本。
Dependency management - 依赖管理
声明在 <dependency></dependency>
中的依赖,就是直接依赖,优先级高于传递依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
Dependency scope - 依赖范围
依赖可以声明自己的范围,表示项目在这个范围内才可以使用该依赖。
this allows you to only include dependencies appropriate for the current stage of the build.
依赖范围有 6 种:
compile
: 默认范围,该依赖可以在任何阶段使用(测试、编译、运行),比如Spring Core
test
: 表示该依赖只在测试时可用,比如JUnit
runtime
: 表示该依赖只在运行时可用,比如jdbc driver
provided
: 表示该依赖应该由容器在运行时提供,比如servlet-api
system
: 跟provided
类似,但是必须自己提供 jar 包,不是从远程仓库下载imported
: 用于从指定 POM<dependencyManagement>
导入依赖
Excluded dependencies - 排除依赖
排除某个传递依赖。
举例:
Y
依赖 Z
;
当 X
依赖 Y
时, X
可以排除 Y
的依赖 Z
,这样 X
就不会依赖 Z
了。
使用场景:
- 解决循环依赖
- 不需要某个传递依赖
- 获取不到某个传递依赖
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions> <!-- 排除依赖 -->
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
Optional dependencies - 可选依赖
类似 排除依赖。
举例:
Y
依赖 Z
,Y
将 Z
声明为 可选依赖;
当 X
依赖 Y
时,X
仅仅依赖 Y
,不会依赖 Z
(当然,X
可以声明直接依赖 Z
)。
<project>
...
<dependencies>
<!-- declare the dependency to be set as optional(声明这个依赖为可选依赖)-->
<dependency>
<groupId>sample.ProjectA</groupId>
<artifactId>Project-A</artifactId>
<version>1.0</version>
<scope>compile</scope>
<optional>true</optional> <!-- value will be true or false only -->
</dependency>
</dependencies>
</project>
依赖的优先级
依赖的优先级关系:
- 子工程
pom.xml
出现的依赖 > 父工程pom.xml
出现的依赖 dependency management
>dependency mediation
(直接依赖优先级 > 依赖仲裁)neareast definition
确定优先级
仓库
- 本地仓库:
local repository
- 远程仓库:
remote repository
mvn install
:打包到 target
,并把包推送到本地仓库。
mvn deploy
:打包到 target
,并把包推送到本地仓库,以及推送到远程仓库。
一般我们创建了 Maven 项目,Maven 会从远程仓库下载依赖,放入本地仓库中,这个过程比较耗时;
以后本地项目优先从本地仓库读取依赖。
如果远程仓库没有我们想要的依赖,Maven 会在本地仓库生成这个依赖的 XXX.jar.lastUpdated
,XXX.pom.lastUpdated
,相当于这个依赖的占位符,同时 Maven 报错 Could not find artifact XXX
。
构建过程
Lifecycle & phase
我们构建一个项目,会经过 验证、编译、测试、打包、部署 等一系列过程。
Maven 用 lifecycle(生命周期)
表示这个构建过程,其中的每一步叫做 phase(阶段)
。
Plugin & goal
Maven 实际的构建工作,交给 plugin(插件)
完成的。
每个 plugin
,由1个或多个 goal
组成。
比如 compiler plugin
有两个 goal
:
compile
:编译scr/main/java
中的源代码testCompile
:编译src/test/java
中的源代码
每个 phase
,可以不绑定 goal
、可以绑定1个或多个 goal
。
- 当不绑定
goal
,执行这个phase
会什么都不做; - 当绑定1个或多个
goal
,执行这个phase
会执行所有的goal
。
Maven 定义了 3 种生命周期
default lifecycle
clean lifecycle
site lifecycle
这 3 种生命周期,由不同的 phase
组成。
下面是它们的定义(https://maven.apache.org/ref/3.8.7/maven-core/lifecycles.html)。
default lifecycle
default lifecycle
的定义文件中,每个 phase
都没有绑定插件;
因此,这个定义文件仅仅声明了 phase
的执行顺序。
<phases>
<phase>validate</phase>
<phase>initialize</phase>
<phase>generate-sources</phase>
<phase>process-sources</phase>
<phase>generate-resources</phase>
<phase>process-resources</phase>
<phase>compile</phase>
<phase>process-classes</phase>
<phase>generate-test-sources</phase>
<phase>process-test-sources</phase>
<phase>generate-test-resources</phase>
<phase>process-test-resources</phase>
<phase>test-compile</phase>
<phase>process-test-classes</phase>
<phase>test</phase>
<phase>prepare-package</phase>
<phase>package</phase>
<phase>pre-integration-test</phase>
<phase>integration-test</phase>
<phase>post-integration-test</phase>
<phase>verify</phase>
<phase>install</phase>
<phase>deploy</phase>
</phases>
每个 phase
绑定什么插件,跟 打包方式 - packaging 有关,由其他文件定义。
联合 default lifecycle
的定义文件,就可以确定不同的 打包方式 下, default lifecycle
每个 phase
的具体操作是什么(可以参看 https://maven.apache.org/ref/3.8.7/maven-core/default-bindings.html)。
打包方式为 pom
绑定的插件
<phases>
<install>
org.apache.maven.plugins:maven-install-plugin:2.4:install
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
</deploy>
</phases>
打包方式为 jar
绑定的插件
<phases>
<process-resources>
org.apache.maven.plugins:maven-resources-plugin:2.6:resources
</process-resources>
<compile>
org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
</compile>
<process-test-resources>
org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
</process-test-resources>
<test-compile>
org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
</test-compile>
<test>
org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
</test>
<package>
org.apache.maven.plugins:maven-jar-plugin:2.4:jar
</package>
<install>
org.apache.maven.plugins:maven-install-plugin:2.4:install
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
</deploy>
</phases>
clean lifecycle
clean phase
绑定插件:org.apache.maven.plugins:maven-clean-plugin:2.5:clean
。
<phases>
<phase>pre-clean</phase>
<phase>clean</phase>
<phase>post-clean</phase>
</phases>
<default-phases>
<clean>
org.apache.maven.plugins:maven-clean-plugin:2.5:clean
</clean>
</default-phases>
site lifecycle
site phase
绑定插件:org.apache.maven.plugins:maven-site-plugin:3.3:site
,
site-deploy phase
绑定插件:org.apache.maven.plugins:maven-site-plugin:3.3:deploy
。
<phases>
<phase>pre-site</phase>
<phase>site</phase>
<phase>post-site</phase>
<phase>site-deploy</phase>
</phases>
<default-phases>
<site>
org.apache.maven.plugins:maven-site-plugin:3.3:site
</site>
<site-deploy>
org.apache.maven.plugins:maven-site-plugin:3.3:deploy
</site-deploy>
</default-phases>
命令行
我们可以使用 Maven 命令行,构建我们的项目。
Maven 命令行可以由多个 phase
+ goal
组合,命令行格式为 mvn xxx xxx
。
一个 phase
属于一个 lifecycle
,在这个 lifecycle
中,该 phase
之前可能有其他 phase
;
当执行一个 phase
,Maven 会先执行这个 phase
之前所有的 phase
,最后再执行这个 phase
。
举个例子,我们执行命令 mvn clean dependency:copy-dependencies package
会:
- 执行
clean lifecycle
中clean
之前所有的phase
(最后执行clean
); - 执行
dependency:copy-dependencies
; - 执行
default lifecycle
中package
之前所有的phase
(最后执行package
);
常见的命令行
命令行 | 用途 |
---|---|
mvn clean | 删除当前项目的 target 目录 |
mvn package | 根据 pom.xml 配置的 打包方式 对项目打包,打出来的包放在当前项目的 target 目录下 |
mvn install | 先打包放在当前项目的 target 目录下,然后把包推送到本地仓库 |
mvn deploy | 先打包放在当前项目的 target 目录下,然后把包推送到本地仓库,最后推送到远程仓库 |
mvn clean install | 相当于 mvn clean + mvn install |
mvn clean deploy | 相当于 mvn clean + mvn deploy |
注:clean
是 clean lifecycle
的一个 phase, install
deploy
是 default lifecycle
中的 phase,所以他们要组合使用。
可重现构建
可重现构建:Reproducible builds
指只要源码相同,每次构建,都会得到相同的产物。
Maven 实现了 可重现构建。
参考资料
- https://maven.apache.org/guides/getting-started/index.html
本文完。