依赖冲突加载顺序
介绍了项目中同时引用了相同依赖的不同版本,也就是冲突,maven 是如何选择的。了解了有助于解决项目中的依赖问题
先说结论:
- 直接依赖的会引用后申明的依赖
- 间接依赖使用路径长度最短的,如果长度一样,则优先申明的最终加载
- 有父 pom 的子 pom 中的会覆盖父 pom 的依赖版本
<dependencyManagement>管理的版本,子模块 pom 中间接依赖的版本也被锁死
优先级(运行子 pom):
<dependencyManagement> > 子 pom(直接依赖) > 父 pom > 间接依赖
直接依赖
<!-- 直接依赖 -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.19.4</version>
</dependency>

项目的 pom 文件直接引用依赖
结论:maven 会引用后申明的依赖
- 这种情况几乎不会有,谁会在一处同时申明2个相同依赖不同版本的依赖,假使出现了,了解即可。
间接依赖
假设 module-starter 的 pom 如下:
<dependency>
<groupId>com.meizi</groupId>
<artifactId>module4</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.meizi</groupId>
<artifactId>module1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

module4 引用 protobuf-java:3.11.4
module1 引用 protobuf-java:2.2.0
最终使用的是 protobuf-java:3.11.4
结论:间接依赖使用优先申明的
假设 module-starter 的 pom 如下:
<dependency>
<groupId>com.meizi</groupId>
<artifactId>module2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.meizi</groupId>
<artifactId>module4</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

module4 引用 protobuf-java:3.11.4 (路径长度1)
module2 引用 module3 引用 protobuf-java:3.19.4(路径长度2)
最终使用的是 protobuf-java:3.11.4
结论:间接依赖使用路径长度最短的,如果长度一样,则优先申明的最终加载;即后面提到的依赖调解
父 pom 中的依赖
父 pom 如下:
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.meizi</groupId>
<artifactId>learnmaven</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>module1</module>
<module>module2</module>
<module>module3</module>
<module>module4</module>
<module>module-starter</module>
</modules>
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.11.4</version>
</dependency>
</dependencies>
</project>
module1 pom 如下:
<?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/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>learnmaven</artifactId>
<groupId>com.meizi</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>module1</artifactId>
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
</project>

结论:子 pom 中的会覆盖父 pom 的依赖版本
- 如果间接依赖和父 pom 中的依赖出现,以父 pom 优先
版本锁定 <dependencyManagement>
-
使用
如下在父 pom 申明:<!-- learnmaven 父pom--> <dependencyManagement> <dependencies> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>2.2.0</version> </dependency> </dependencies> </dependencyManagement> -
子模块 pom 可不用写版本
子模块中与 dependencyManagement 匹配的最小信息集是这四个 {groupId, artifactId, type, classifier},因为在大多数情况下都是 type=jar classifier=null,所以如果不写,maven默认,如果不是jar的需要指定。<!-- module-starter 子pom--> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> </dependency> -
子模块 pom 中间接依赖的版本也被锁死
<!-- module-starter 子pom--> <dependency> <groupId>com.meizi</groupId> <artifactId>module4</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.meizi</groupId> <artifactId>module3</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
从可传播依赖并入的依赖的版本也受此限制。
依赖冲突解决
了解了依赖冲突加载顺序,一定程度上可以解决冲突。 由于没有依赖传播级数的限制,会有可能出现循环依赖。 所以有一些方式来限制依赖传播
依赖调解
依赖调解第一原则:最短路径
最短路径 项目的依赖树最近的依赖
如下依赖:
A
├── B
│ └── C
│ └── D 2.0
└── E
└── D 1.0
会使用 D 1.0,因为他的路径最短,如果你想使用 D 2.0 可以在 A 项目中直接添加 D 2.0 依赖,如下
A
├── B
│ └── C
│ └── D 2.0
├── E
│ └── D 1.0
│
└── D 2.0
依赖调解第二原则:优先申明
如果2个依赖树的路径长度相同,那么先声明的会被解析使用
版本锁定
即 <dependencyManagement>,使用见上文
依赖作用域
即 <scope> 作用域,限制依赖传递和决定何时依赖包含在类路径中,类路径(classpath)有三种,分别是:
- 编译类路径
- 测试类路径
- 运行类路径
提供如下选项:
- compile 默认,编译依赖项在项目的所有类路径中都可用。此外,这些依赖关系被传播到依赖的项目。
- test 表明该依赖在正常使用中不是必须的,并且仅在测试编译和执行阶段可用,这个作用域不是可传递的
- runtime 在编译中不需要,在执行中需要
- provided 类似compile,表明这个依赖是提供的,在运行时会有外部提供(例如tomcat),仅在编译和测试,是不可传递的
- system 除了您必须提供显式包含它的JAR之外,此作用域与
provided的作用域类似。此类依赖不通过maven仓库解析。 - import 只用在pom的
<dependencyManagement>部分,它指示该依赖项将被指定POM的<dependencyManagement>部分中的有效依赖项列表所替换。
排除依赖
通过使用 exclusion 可以将不需要的依赖排除掉
<dependency>
<groupId>com.meizi</groupId>
<artifactId>module4</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
此时,引入module4,将不会引入 protobuf-java。
可选依赖
即 <optional>,默认false不可选,可传播; true 可选,可认为是被排除了,不可传播
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.11.4</version>
<!-- 默认false不可选,可传播; true 可选,可认为是被排除了,不可传播 -->
<optional>true</optional>
</dependency>
如果想排除此依赖,也可将 optional 设置为 true
理想情况下是不应该使用可选依赖的,理由是单一职责,一个项目不能既要又要,最好是拆成2个artifactid,引用后直接传递使用
依赖管理
导入依赖
在大型项目中,很难通过继承管理所有依赖,因为 maven 是单继承的。
为了解决这个,项目可以从其他项目导入。通过申明一个 type 是 pom,scope 是 import 的工件
<!-- learnmaven 父pom-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>4.1.29.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
所有 netty-bom 中 dependencyManagement 所管理的都会被合并过来。
<!-- learnmaven 父pom-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>4.1.79.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>4.1.29.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
如上,4.1.79.Final 将被使用,因为他先申明
并且导入是递归的,例如,netty-bom 导入了另一个 pom,那么这个项目也会导入
bom poms
Bill of Materials (BOM) 材料清单
参考文献
- maven.apache.org/guides/intr…
- 《Maven实战》-许晓斌