运行 Maven 的语法:
mvn [options] [] []
命令一: mvn clean compile
, 编译.
clean
用于清除 target
目录, compile
用于编译项目主代码(src/main/java
).
Maven 先执行了 clean:clean 任务, 删除 target/
目录; 接着执行 resources:resources 任务; 最后执行 compiler:compile 任务, 将项目主代码编译到 target/classes
目录.
命令二: mvn clean test
执行单元测试.
命令三: mvn clean package
或者 mvn clean package -Dmaven.test.skip=true
.
打包(默认类型jar), 使用-Dmaven.test.skip=true
选项可以在打包时跳过单元测试.
命令四: mvn clean install
将当前Maven项目(构件) 安装到 本地仓库(~/.m2/repository
).
命令五: mvn clean deploy
将当前Maven项目(构件) 上传/部署 到 远程仓库(~/.m2/setting.xml
中配置的仓库).
命令六: mvn help:system
打印出所有 Java 系统属性 和 环境变量.
当我们从中央仓库引入一个依赖时, 需要指定 groupId
, artifactId
和 version
这 3 个元素.
groupId
, artifactId
和 version
就是 Maven 的坐标.
我们自己的 Maven 项目 也必须 在 pom.xml
中指定 其 坐标, 例如:
<groupId>cn.mitrecx.myappgroupId>
<artifactId>myapp-utilartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
groupId
指定 Maven 项目组名, 通常是公司/组织 域名反写 加 项目名.
artifactId
指定 Maven 项目/构件 名.
version
指定当前 Maven 项目 的版本.
packaging
指定 Maven 项目打包方式, 默认就是 jar
, 可以不写.
管理单个项目的依赖关系很容易. 管理由 数百个模块 组成的 多模块项目(multi-module projects) 和应用程序的依赖关系是可能的. Maven 在定义, 创建和维护 具有良好定义的类路径和库版本的 可复制构
建(reproducible builds) 方面帮助很大.
POM 中每个依赖可以包含的元素有:
packaging
. 大部分情况下, 该元素不用声明, 默认值是 jar
. 在依赖管理中, 被导入的 BOM 会使用 pom
而不是 jar
.假设项目A
依赖 项目B
:
A
├── B
└── C
└── D 2.0
一般情况下, 当我们在项目中引入依赖 A
时, Maven 会自动的把 B, C, D2.0
引入到当前项目中.
只所以说 “一般情况下”, 是因为依赖的 scope, optional, exclusions 元素取值可能会导致 依赖传递性 失效.
考虑以下情况:
A
├── B
│ └── C
│ └── D 2.0
└── E
└── D 1.0
B
和 E
都引入了 依赖 D
, 而且 D
的版本不同, 那么在 A
使用使用 D
时, 会使用哪个版本的呢?
Maven 使用依赖调节解决这个问题, 其策略是 最短路径优先(nearest definition).
通过 E
实现 A->D
的依赖路径更短, 所以 A
会依赖 D1.0
.
可以通过以下方式强制指定 A
依赖 D2.0
:
A
├── B
│ └── C
│ └── D 2.0
├── E
│ └── D 1.0
│
└── D 2.0
如果 依赖路径长度相同 的话, Maven 会采用 最先声明优先 策略, 即在 POM 中声明依赖靠前的依赖被使用.
如果出现 依赖冲突, 可以利用 最短路径优先 或 最先声明优先 或 exclusions
(后面介绍) 来解决冲突.
optional 元素声明的依赖 不会被传递:
比如, 项目 A 引入一个 optional
的依赖 B, 表示为: A->B(optional)
;
X 引用依赖 A, 表示为 X->A
;
那么在 X 里 不会传递引入 依赖 B.
依赖范围 用于限制 依赖的可传递性, 并确定依赖 何时 包含在类路径(classpath)中.
Maven 中有 6 种依赖范围:
1, compile
compile
依赖范围 是默认的依赖范围. compile
依赖范围 对所有的 classpath 都生效, 即 对 编译(compilation) classpath, 测试(test) classpath, 运行(runtime) classpath 都生效.
2, provided
provided
依赖范围 只对 编译 classpath 和 测试 classpath 生效, 对 运行 classpath 无效.
当 JDK 或 容器 在 运行时 提供依赖项时, 我们就不需要在运行时指定依赖项了.
比如, servlet-api, 在运行项目时, 容器已经 提供 此依赖项.
provided
不可传递.
3, runtime
对 运行和测试 classpath 有效, 但在编译主代码时无效.
4, test
此范围表示应用程序的正常使用不需要此依赖项. 仅在 单元测试阶段 使用(仅对测试 classpath 生效).
通常, 此范围用于测试库, 如 JUnit
和 Mockito
.
test
不可传递.
5, system
和 provided
的依赖范围一致. 但需要使用 systemPath
指定依赖文件的路径, 这个路径是和本地系统绑定的.
6, import
import
依赖范围 只支持在 dependencyManagement
中的依赖中使用.
它表示要用 dependencyManagement
中的有效依赖项列表 替换 依赖项.
该依赖范围不会对 3 种 classpath 产生影响.
看一个官网给的例子,
假设我们有如下依赖关系:
Project-A
-> Project-B
-> Project-D <! -- This dependency should be excluded -->
-> Project-E
-> Project-F
-> Project C
Project-A 同时依赖于 Project-B 和 C.
Project-B 依赖于 Project-D.
Project-D 同时依赖于 Project-E 和 F.
默认情况想, Project-A 的 classpath 会包含:
B, C, D, E, F
假设你不想用 Project-D 和它的依赖 被添加到 Project-A 的 classpath 中, 并且你不需要 Project-B 依赖 Project-D 提供的功能. 理想情况下 Project-B 的开发者标记了 依赖 Project-D
, 这样就需要我们做什么:
<dependency>
<groupId>sample.ProjectDgroupId>
<artifactId>ProjectDartifactId>
<version>1.0-SNAPSHOTversion>
<optional>trueoptional>
dependency>
但如果他们并没有用 optional
标记 Project-D 时, 我们可以 用exclusion
元素 排除 它:
<project>
<modelVersion>4.0.0modelVersion>
<groupId>sample.ProjectAgroupId>
<artifactId>Project-AartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
...
<dependencies>
<dependency>
<groupId>sample.ProjectBgroupId>
<artifactId>Project-BartifactId>
<version>1.0-SNAPSHOTversion>
<exclusions>
<exclusion>
<groupId>sample.ProjectDgroupId>
<artifactId>Project-DartifactId>
exclusion>
exclusions>
dependency>
dependencies>
project>
如果我们把 Project-A 部署(deploy) 到 仓库(repository), Project-X 声明了一个 对 Project-A 的普通依赖:
Project-X -> Project-A
这种情况, Project-D 依然会被排除, 即, Project-D 不会被包含在 Project-X 的 classpath 中.
说到 dependencyManagement
, 要先了解一下 Maven 继承. 因为这俩哥们通常都是一起干活的.
Maven 继承 和 Java 继承一样, 只能 单一继承. 即, 一个 子项目 只能有一个 父项目.
在 子项目的 POM 文件中, 使用 parent
元素引入 父项目 即可 继承, 例如:
<parent>
<groupId>com.testgroupId>
<artifactId>bomartifactId>
<version>1.0.0version>
parent>
子项目 继承 父项目, 继承了啥呢 ?
这里给一个完整的列表:
dependencyManagement
元素只用于声明依赖, 并不引入依赖(不用引入到 classpath 中), 因此子项目需要显式的在dependency
中引入需要用的依赖.
官网给了两个例子 对理解 继承 和 依赖管理 很有帮助. 例子很简单, 但是 配置文件很冗长(不用担心, 耐心看 3 分钟左右就能看完, 强烈建议看一下). 就这两个例子, 我们可以有以下结论:
1, 子项目的依赖 优先于 父项目的依赖. 比如, 子项目和父项目同时引入了依赖A, 但是版本不同, Maven 会以子项目的版本为准.
2, 依赖管理 优先于 依赖调节.
3, dependency
优先于 dependencyManagement
. 比如, dependencyManagement
声明了 依赖 A 的版本为 1.0, 如果我们在 dependency
引入依赖时不写 version
, 那么默认就是引入 A1.0, 如果在 dependency
中引入依赖时指定 version
为 2.0, 那么会引入 A2.0 到 classpath.
由于只能 继承 一个父项目, 当需要使用来自不同项目的多个 dependencyManagement
时, 继承就不好使了.
这种情况下, 我们可以使用
导入 依赖管理.
示例:
<project>
<modelVersion>4.0.0modelVersion>
<groupId>mavengroupId>
<artifactId>BartifactId>
<packaging>pompackaging>
<name>Bname>
<version>1.0version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mavengroupId>
<artifactId>AartifactId>
<version>1.0version>
<type>pomtype>
<scope>importscope>
dependency>
...
dependencyManagement>
...
project>