昨晚周会,接到了一个“任务”,对目前组内一些项目的 Maven 依赖做一个梳理。其实谈起 Maven,是一个很有意思的点,在越来越卷的 Java 行业,动不动就是“分布式、高并发、架构设计”,还得让你从 JVM 的源码来分析下 synchornized
,但很少会有人提到 Maven,其实相比那些花里胡哨的,Maven 才是真正与日常开发息息相关的“基本功”,因 Maven 引发的问题也不少见。本文主要探讨的议题就是 Maven。
一些命令
mvn help:system
这个命令会打印出所有的 Java 系统属性和环境变量。
比如有时候想知道 Java 的安装目录、Maven 的安装目录,IO 的一些临时文件目录等,都可以通过这个命令获取。
➜ ~ mvn help:system
...
===============================================================================
System Properties
===============================================================================
java.runtime.name=OpenJDK Runtime Environment
sun.boot.library.path=/Users/dongguabai/develope/openJdk/jdk8u202-b08/Contents/Home/jre/lib
java.vm.version=25.202-b08
gopherProxySet=false
java.vm.vendor=Oracle Corporation
...
===============================================================================
Environment Variables
===============================================================================
JAVA_7_HOME=/Users/dongguabai/develope/java7/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home
ZSH=/Users/dongguabai/.oh-my-zsh
LDFLAGS=-L/usr/local/opt/openssl/lib
LIBRARY_PATH=:/usr/local/opt/openssl/lib/
JAVA_MAIN_CLASS_39050=org.codehaus.plexus.classworlds.launcher.Launcher
USER=dongguabai
JAVA_HOME=/Users/dongguabai/develope/openJdk/jdk8u202-b08/Contents/Home
TERM=xterm-256color
CLASSPATH=/lib/tools.jar:/lib/dt.jar:.
MAVEN_CMD_LINE_ARGS= help:system
MAVEN_PROJECTBASEDIR=/Users/dongguabai
...
Archetype 构建
Maven 提供了 Archetype 帮助我们快速构建项目骨架。
Maven 3.x+ 可以直接执行 mvn archetype:generate
,会自动使用稳定版的 archetype 插件。
➜ mavenTemp mvn archetype:generate
[INFO] Scanning for projects...
...
Define value for property 'groupId': com.dongguabai
Define value for property 'artifactId': dongguabai-maven-demo
[INFO] Using property: version = 1.0-SNAPSHOT
Define value for property 'package' com.dongguabai: : 1.0-SNAPSHOT
Confirm properties configuration:
groupId: com.dongguabai
artifactId: dongguabai-maven-demo
version: 1.0-SNAPSHOT
package: 1.0-SNAPSHOT
Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: /Users/dongguabai/Desktop/temp/mavenTemp
[INFO] Parameter: package, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: com.dongguabai
[INFO] Parameter: artifactId, Value: dongguabai-maven-demo
[INFO] Parameter: packageName, Value: 1.0-SNAPSHOT
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /Users/dongguabai/Desktop/temp/mavenTemp/dongguabai-maven-demo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 58.184 s
[INFO] Finished at: 2021-11-23T08:26:19+08:00
[INFO] Final Memory: 15M/206M
[INFO] ------------------------------------------------------------------------
构建的过程中会让你选择 groupId
、artifactId
和 version
。整个目录如下:
➜ mavenTemp tree .
.
└── dongguabai-maven-demo
├── pom.xml
└── src
├── main
│ └── java
│ └── 1
│ └── 0-SNAPSHOT
│ └── App.java
└── test
└── java
└── 1
└── 0-SNAPSHOT
└── AppTest.java
10 directories, 3 files
这个结构展示了 Maven 的一些约定:在项目的根目录中放置 pom.xml,在 src/main/java
目录中放置项目的主代码,在 src/test/java
中放置项目的测试代码。
dependency 依赖
<dependency>
就是标识要引用的依赖。
<dependency>
<groupld>..</groupld>
<artifactld>... </artifactld>
<version>..</version>
<type> ..</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
</exclusion>
</exclusions>
</dependency>
接下来介绍一下依赖中的几个关键元素。
pom.xml 元素
type
表示依赖的类型,对应于项目坐标定义的 packaging
。大部分情况下,该元素不必声明,其默认值为 jar
。
其实 Maven 坐标除了我们常用到的 groupId
、artifactId
和 version
之外,还有 packaging
和 classifier
。 packaging
就是定义的 Maven 项目的打包方式, classifier
我们不能直接定义。
scope
表示依赖的范围,这个是平时开发很常用的。
先说一下什么是 Java 中的 classpath
,它说白了就是告诉 JVM,你从哪个路径上找 class
。
scope
就是用来控制依赖与下面三种 classpath
的关系:
- 编译
classpath
; - 测试
classpath
; - 运行
calsspath
。
Maven 主要有以下几种依赖范围:
-
compile
:编译依赖范围,如果没有指定,就会默认使用该依赖范围。使用此依赖范围的 Maven 依赖,对于编译,测试和运行三种 classpath
都有效。 -
test
:测试依赖范围。使用此依赖范围的 Maven 依赖,只对于测试 classpath
有效,在编译主代码或者运行项目使用时将无法使用此依赖。典型的例子就是 Junit。
- 说白了就是在
src/main/java
目录中放置的项目的主代码是用不到的。仅对 src/test/java
中的测试代码有效。在项目发布的时候可以减少无用依赖的引入。
-
provided
:已提供依赖范围。使用此依赖范围的 Maven 依赖,对于编译和测试 classpath
有效,但在运行项目的时候无效。典型的例子就是 servlet-api,编译和测试项目时候,需要该依赖,但是在运行项目时,由于容器已经提供,就不需要 Maven 重复的引入一遍。
- 说白了就是编译、测试、运行都要用到,但是打包的时候不要,可能外部容器已经提供了。
-
runtime
:运行时依赖范围。使用此依赖的 Maven 依赖,对于测试和运行的 classpath
有效,但在编译主代码时无效。典型的例子就是 JDBC 驱动实现。项目主代码的编译只需要 JDK 提供的 JDBC 接口,只有在执行测试或者运行项目时才需要实现上述接口的具体 JDBC 驱动。
- 说白了就是这个依赖项目不会参与项目的编译,不过后期的测试和运行周期需要其参与。与
compile
的区别就是跳过了编译。但是这里有几个细节要注意,这里的“不参与编译”并不是说这个依赖的内容就不编译了(我们通过 Maven 引入的包,里面的类,都是已经编译好的 class
),是指这个依赖不参与我们主代码的编译,以上文提到的 JDBC 为例,MySQL 依赖如果是 runtime
,那么它里面的内容我们是无法使用的,只能使用 JDBC 接口,反正有点“强制使用接口”的意思。
optional
表示可选依赖。关于可选依赖可以参看《从 Spring Boot 自动装配看 Maven 可选依赖的使用》。
exclusions
exclusions
里面就是一堆 exclusion
,表示排除依赖。需要注意的是,声明 exclusion
的时候只需要 groupld
和 artifactld
,而不需要 version
元素,这是因为只需要 groupld
和 artifactld
就能唯一定位依赖,换句话,Maven 解析后的依赖,不可能出现 groupld
和 artifactld
相同,而 version
不同的情况。
其他问题
依赖传递
这个很好理解,比如 B 依赖了 C,A 又依赖了 B,那么 A 就间接依赖了 C(如果没有配置可选依赖的话)。
依赖范围对传递依赖的影响
上文说的是在默认 scope
配置(compile
)的情况,直接 B 依赖了 C,A 又依赖了 B,那么 A 就间接依赖了 C。这里 A 对 B 是第一直接依赖,B 对于 C 是第二直接依赖,A 对于 C 是间接依赖。
不同的依赖范围是会影响依赖传递的效果的,这里有一张表格:
第一列是第一直接依赖范围,第一行第二直接依赖范围,比如 A 对 B 第一直接依赖,范围是 test
,B 对 C 第二直接依赖,范围是 compile
,根据上面的表格,A 对 C 对间接依赖范围就是 test
。
依赖调解
依赖调解就是来解决这样的问题:A->B->C->D(1.0),A->M->D(2.0),根据依赖传递,A 同时依赖了两个版本不同的 D。那么 Maven 解析后 A 到底依赖 D 的哪个版本呢?
Maven 有两个依赖调节原则(分先后):
- 路径最近优先
a. 比如上面的例子,A 距离 D(2.0)更近,所以 Maven 会解析D(2.0)。 - 第一声明优先
a. 还有一种情况 A->B->D(1.0),A->M->D(2.0),A 距离两个版本的 D 的距离是一样的。这时候就看在 pom.xml 中依赖声明的顺序,顺序最靠前的那个依赖优胜。如果 B 的依赖声明在 M 之前,那么D(1.0)就会被解析。
平时开发中出现的依赖冲突就与依赖调解有关,其实就是因为 Maven 选出来的版本对某些依赖不适用。
References
- 《Maven 实战》
- https://mp.weixin.qq.com/s?__biz=MzU1OTgyMDc3Mg==&mid=2247484104&idx=1&sn=badb7959e88bf20a7dbf409d972da712&chksm=fc103843cb67b155a78e1d1ee233e341fc2805fb4549d8c20536e2e2280c8db4b7a142456b5d&token=2068452739&lang=zh_CN#rd