Maven 依赖可以分为如下几部分:
- 直接依赖,就是本项目 dependencies 部分的依赖
- 间接依赖,就是本项目 dependencies 部分的依赖所包含的依赖
- 依赖管理,就是本项目 dependency management 里面的依赖
- parent 的直接依赖
- parent 的间接依赖
- parent 的依赖管理
- bom 的直接依赖(一般没有)
- bom 的间接依赖(一般没有)
- bom 的依赖管理
PS: bom 就是工程项目中最外层主 POM ,也就是 dependencyManagement 那个 POM
一、 MAVEN 依赖三大原则
1.1 最短路径优先原则
Maven 依赖遵循最短路径优先原则,当项目直接依赖一个 C-api-1.0 和 A-api-2.1 包,并且 C-api-1.0 有如下间接依赖关系: C-api-1.0 ---> B-api-1.0 ---> A-api-1.1 这时候项目里包含了 A-api 的 1.1 和 2.1 两个版本,由于存在最短路径原则明显 Project ---> A-api-2.1 短于 Project ---> C-api-1.0 ---> B-api-1.0 ---> A-api-1.1 故 Project 项目里会使用 A-api-2.1
示例项目中包含如下依赖
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<artifactId>QLExpress</artifactId>
<groupId>com.alibaba</groupId>
<version>3.2.2</version>
</dependency>
</dependencies>
其中 poi-ooxml 存在如下依赖关系: poi-ooxml-->poi-->commons-logging (版本 1.1 ) QLExpress 存在如下依赖关系: QLExpress ---> commons-logging (版本 1.1.1 ) 由于存在最短路径原则,明显 QLExpress ---> commons-logging 路径更短,项目会使用 commons-logging 的 1.1.1 版本
1.2 POM 文件中申明顺序优先原则
Maven 依赖遵循 POM 文件中申明顺序优先原则,当项目里存在直接依赖 C-api-1.0 和 B-api-1.0 其中存在如下间接依赖关系: C-api-1.0 ---> A-api-2.1 B-api-1.0 ---> A-api-1.1 这时项目间接依赖了 A-api 的 2.1 和 1.1 两个版本,由于存在 POM 文件中申明顺序优先原则,故项目中会使用 A-api-2.1 示例项目中存在如下依赖
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.17-beta1</version>
</dependency>
</dependencies>
其中 poi-ooxml 依赖了包 poi ,poi-scratchpad 也依赖了 poi 包,但是 poi-ooxml 依赖了 poi 包的 3.10-FINAL 版本, poi-scratchpad 依赖了 poi 包的 3.17-beta1 版本,由于存在申明顺序优先原则,项目会使用 poi 包的 3.10-FINAL 版本
1.3 覆盖优先原则
Maven 依赖遵循覆盖优先原则,项目父 POM 中直接依赖包 A-api-1.1 ,子模块 Module A 的 parent 直接依赖了项目的 POM ,但是同时也直接依赖了 A-api-1.2 。 由于存在覆盖优先原则子模块 Module A 中会优先使用 A-api-1.2 而不是父POM的 A-api-1.1 在以上项目工程下,新建一个子工程,在子工程POM添加如下依赖
xml
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
</dependencies>
由于外部工程依赖了 poi 包的 3.10-FINAL ,但是子工程依赖了 poi 的 5.2.3 版本,故整体包依赖会包含poi的两个版本,但是在子工程中使用的是 5.2.3 怎么证明我们在子工程使用的是 poi 的 5.2.3 版本呢? 我们在子工程可以写一个 main 方法调用方法 org.apache.poi.util.Units 类的 columnWidthToEMU 方法,在父工程同样调用这个方法,发现父工程这个方法报错不存在。
typescript
import org.apache.poi.util.Units;
public class Test {
public static void main(String[] args) {
Units.columnWidthToEMU(1);
}
}
二、 MAVEN 依赖冲突常见报错
2.1 ClassNotFoundException
当项目启动时出现 ClassNotFoundException 这样的错误,表示由于项目使用的包版本下找不到当前需要的类 1、调用 class 的 forName 方法时,找不到指定的类。 2、 ClassLoader 中的 findSystemClass() 方法时,找不到指定的类。 3、 ClassLoader 中的 loadClass() 方法时,找不到指定的类。
2.2 NoSuchMethodError
NoSuchMethodError 就是程序在运行中找不到运行的方法导致的 1、有可能发生的就是 jar 冲突,可能是两个高低版本的 jar 包导致。 2、有可能是有两个 jar 包有相同的类与方法,导致程序调用过程中找不到正确的方法。
三、Maven 依赖加载流程
Maven 依赖加载流程如下
- 首先,将 parent 的直接依赖,间接依赖,还有依赖管理,插入本项目,放入本项目的直接依赖,间接依赖还有依赖管理之前。
- 对于直接依赖,如果有 version,那么就依次放入 DependencyMap 中。如果没有 version ,则从依赖管理中查出来 version,之后放入 DependencyMap 中。 key 为依赖的 groupId + artifactId, value 为 version ,后放入的会把之前放入的相同 key 的 value 替换。
- 对于每个依赖,各自按照步骤 1 和 2 加载自己的 pom 文件,但是如果第一步中的本项目 dependency management 中有依赖的版本,使用本项目 dependency management 的依赖版本,生成 TransitiveDependencyMap ,这里面就包含了所有的间接依赖。
- 所有间接依赖的 TransitiveDependencyMap , 对于项目的 DependencyMap 里面没有的 key ,依次放入项目的 DependencyMap 。
- 如果 TransitiveDependencyMap 里面还有间接依赖,那么递归执行步骤 3 和 4 。
由于是先放入本项目的 DependencyMap ,再去递归 TransitiveDependencyMap ,这就解释了 Maven 依赖的最短路径原则。 可用文中 1.1 做示例如下:
四、总结
本次主要讲解了 maven 依赖包含的几大部分,以及 maven 依赖三大原则,并且对每种依赖原则都做了具体图解和示例,方便我们可以直接在项目中运行调试。期间对 maven 冲突导致的常见报错进行讲解方便我们在开发过程中快速定位问题。最后讲解了 maven 加载包进入项目中的整个流程,并按照文中 1.1 所讲的"最短路径优先原则"做为示例图解,进而加深对 maven 加载包流程的理解。
推荐阅读
招贤纳士
政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。规模 500 人左右,在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。
如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊......如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com
微信公众号
文章同步发布,政采云技术团队公众号,欢迎关注