当前位置: 首页 > news >正文

Maven-依赖管理机制

一、背景和起源

依赖管理是Maven的一个核心功能。管理单模块项目的依赖相对比较容易,管理多模块项目依赖或者有几百个模块项目的依赖就是一个巨大的挑战。
如果手动构建项目,那么就先需要梳理各个模块pom中定义的依赖,然后将依赖下载到本地,并且还需要将依赖中pom定义的依赖下载,如此反复。这中间需要解决相同依赖版本不同、依赖排除等情况。

二、依赖配置

项目对外部库的依赖,可以配置在pom文件的dependency节点,需要提供依赖的groupId,artifactId,version。例如:

<dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-mapreduce-client-core</artifactId><version>3.2.2</version><scope>provided</scope><exclusions><exclusion><groupId>com.google.guava</groupId><artifactId>guava</artifactId></exclusion></exclusions>
</dependency>

三、传递依赖

传递依赖机制是指项目只需要在POM中定义直接依赖、不需要定义任何间接依赖。Maven会读取各直接依赖的POM,将必要的间接依赖引入到当前项目。传递依赖机制简化了POM的配置,也将开发者从依赖的复杂传递关系中解脱出来。

1.依赖仲裁机制

当项目出现多版本依赖时,maven就需要通过依赖仲裁机制决定选择哪个版本依赖。

  • 路径优先:依赖层级越浅优先越高,层级越深优先级越低
  • 声明优先:当依赖所在层级相同,声明靠前的依赖优先级高于声明靠后的优先级

2.依赖范围

依赖范围会限制一些依赖在传递依赖机制中的传递,也就是一些间接依赖可能不会引入到当前项目中。传递范围Scope主要分为六种:

  • compile: 编译期,scope默认范围。也就是从编译期直到运行期都需要此依赖。
  • provided: 表示运行容器或者jdk提供的依赖,只需要在编译期引入此依赖,打包成运行程序时不需要包含此依赖。
  • runtime: 表示测试和运行期需要引入此依赖,编译期不需要此依赖,但打包时需要包含此依赖。
  • test: 测试期需要引入此依赖,编译期和打包时都不需要包含此依赖
  • system: 与provided类似,运行期间由用户指定的系统路径提供依赖
  • import:用于引入外部定义的依赖版本管理文件,相当于将依赖版本声明在pom的dependencyManagement节点。

以上依赖范围的依赖在整个构建和运行期间起作用范围如下:
在这里插入图片描述

3.依赖范围对传递的限制

项目A的pom中定义依赖项目B并且Scope为X,项目B中pom中定义依赖项目C并且Scope为Y。根据Maven传递依赖的机制,项目A不仅会加载直接依赖B还会加载间接依赖C,但是以上Scope X和Y会对是否加载依赖C以及依赖C的作用范围产生影响。

传递性依赖范围影响受到Scope X和Scop Y的影响如下:
在这里插入图片描述
注意:列为Scope X,行为Scope Y,交叉部分为项目A中针对依赖C的作用范围,当表格中为‘-’时,表示项目A不会引入依赖C。

4.排除依赖

如果当前项目只想引入直接依赖和部份间接依赖,这样需要将某个间接依赖排除掉,这样可以通过设置exclusions来实现。

比如以下例子就是排除间接依赖guava包。

<dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-mapreduce-client-core</artifactId><version>3.2.2</version><scope>provided</scope><exclusions><exclusion><groupId>junit</groupId><artifactId>junit</artifactId></exclusion></exclusions>
</dependency>

5.可选依赖

如果当前项目在被依赖时,默认情况下想将当前项目的直接依赖不被加载可以用可选依赖。功能相当于当前项目被引用时配置了exclusions节点。

比如以下例子就是排除间接依赖guava包。

<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><!-- 配置不透明 --><optional>true</optional>
</dependency>

四、依赖集中管理

pom中dependencies节点可以定义项目需要引用的依赖,如果是一个多module项目,那么每个module中pom定义的依赖可能出现版本不一致,可能会出现版本冲突并且在升级版本时也不方便集中管理。所以可以在pom中的dependencyManagement节点对依赖的版本、范围、排除项等进行集中管理,这样整个项目中的版本可以保持一致并且方便进行版本管控。

1.父pom集中管理

当项目中有多个module时,可以在项目pom的dependencyManagement节点对依赖进行集中管理,来决定引入依赖的版本、排除间接依赖、依赖范围等。module中的pom将项目pom作为父pom,module中的pom只定义dependencies节点来决定引入哪些依赖。

1.1 不使用dependencyManagement对依赖进行管理

如果项目不是用dependencyManagement管理,则两个项目的pom可以如下进行配置,其中依赖必须包含{groupId, artifactId, type}。
Project A POM:

<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><dependency><groupId>group-a</groupId><artifactId>artifact-b</artifactId><version>1.0</version><type>bar</type><scope>runtime</scope></dependency></dependencies>
</project>

Project B POM:

<project>...<dependencies><dependency><groupId>group-c</groupId><artifactId>artifact-b</artifactId><version>1.0</version><type>war</type><scope>runtime</scope></dependency><dependency><groupId>group-a</groupId><artifactId>artifact-b</artifactId><version>1.0</version><type>bar</type><scope>runtime</scope></dependency></dependencies>
</project>

1.2 使用dependencyManagement对依赖进行管理

为了简化和统一管理,可以将以上两个项目所有的依赖版本管理统一放到父pom中,这样两个项目中依赖定义可以只定义 {groupId, artifactId}。
Parent Project POM:

<project>...<dependencyManagement><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><dependency><groupId>group-c</groupId><artifactId>artifact-b</artifactId><version>1.0</version><type>war</type><scope>runtime</scope></dependency><dependency><groupId>group-a</groupId><artifactId>artifact-b</artifactId><version>1.0</version><type>bar</type><scope>runtime</scope></dependency></dependencies></dependencyManagement>
</project>

两个项目中的pom配置可以简化为:
Project A POM:

<project>...<dependencies><dependency><groupId>group-a</groupId><artifactId>artifact-a</artifactId></dependency><dependency><groupId>group-a</groupId><artifactId>artifact-b</artifactId><!-- This is not a jar dependency, so we must specify type. --><type>bar</type></dependency></dependencies>
</project>

Project B POM:

<project>...<dependencies><dependency><groupId>group-c</groupId><artifactId>artifact-b</artifactId><!-- This is not a jar dependency, so we must specify type. --><type>war</type></dependency><dependency><groupId>group-a</groupId><artifactId>artifact-b</artifactId><!-- This is not a jar dependency, so we must specify type. --><type>bar</type></dependency></dependencies>
</project>

2.当前pom和父pom一起集中管理

依赖集中管理也支持传递依赖,也就是集中管理当pom和父pom都配置了,这两个都会对依赖起到管理作用。
例如Project B继承了Project A,并且两个都定义了依赖管理。
Project A POM:

<project><modelVersion>4.0.0</modelVersion><groupId>maven</groupId><artifactId>A</artifactId><packaging>pom</packaging><name>A</name><version>1.0</version><dependencyManagement><dependencies><dependency><groupId>test</groupId><artifactId>a</artifactId><version>1.2</version></dependency><dependency><groupId>test</groupId><artifactId>b</artifactId><version>1.0</version><scope>compile</scope></dependency><dependency><groupId>test</groupId><artifactId>c</artifactId><version>1.0</version><scope>compile</scope></dependency><dependency><groupId>test</groupId><artifactId>d</artifactId><version>1.2</version></dependency></dependencies></dependencyManagement>
</project>

Project B POM:

<project><parent><artifactId>A</artifactId><groupId>maven</groupId><version>1.0</version></parent><modelVersion>4.0.0</modelVersion><groupId>maven</groupId><artifactId>B</artifactId><packaging>pom</packaging><name>B</name><version>1.0</version><dependencyManagement><dependencies><dependency><groupId>test</groupId><artifactId>d</artifactId><version>1.0</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>test</groupId><artifactId>a</artifactId><version>1.0</version><scope>runtime</scope></dependency><dependency><groupId>test</groupId><artifactId>c</artifactId><scope>runtime</scope></dependency></dependencies>
</project>

当构建项目B时,依赖a、依赖b、依赖c 都是版本1.0被使用。

  • 依赖a:由于在项目B中直接定义了版本号和scope,所以最终是加载的依赖a版本号为1.0、scope为runtime。
  • 依赖c:由于在项目B中只定义了scope,所以版本号需要由dependencyManagement节点决定,所以最终是加载的依赖c版本号为1.0、scope为runtime。
  • 依赖b:如果依赖b被依赖a或者依赖c间接引用,那么依赖b的版本由父pom中dependencyManagement节点决定,所以最终是加载的依赖b版本号为1.0、scope为compile。
  • 依赖d:因为项目B和父pom都在dependencyManagement节点定义了依赖d,所以管理节点是项目B中为准。

3.引入外部集中管理

如果项目module过多,没办法继承同一个父pom。这样可以通过引入一个外部的pom,这个pom只有dependencyManagement节点。

五、依赖版本确定流程

依赖版本的查找与依赖传递的解决机制都是基于项目Pom,也就是当项目有多个module时,是会按照每个module的pom为基本单位进行依赖的查找和传递。
例如:有父项目ProjectA,下边有子项目ProjectB和ProjectC。子项目ProjectC依赖ProjectB。

Project A POM:

<project><modelVersion>4.0.0</modelVersion><groupId>maven</groupId><artifactId>A</artifactId><packaging>pom</packaging><name>A</name><version>1.0</version><dependencyManagement><dependencies><dependency><groupId>test</groupId><artifactId>a</artifactId><version>1.2</version></dependency><dependency><groupId>test</groupId><artifactId>b</artifactId><version>1.0</version><scope>compile</scope></dependency><dependency><groupId>test</groupId><artifactId>c</artifactId><version>1.0</version><scope>compile</scope></dependency><dependency><groupId>test</groupId><artifactId>d</artifactId><version>1.2</version></dependency></dependencies></dependencyManagement>
</project>

Project B POM:

<project><parent><artifactId>A</artifactId><groupId>maven</groupId><version>1.0</version></parent><modelVersion>4.0.0</modelVersion><groupId>maven</groupId><artifactId>B</artifactId><packaging>pom</packaging><name>B</name><version>1.0</version><dependencyManagement><dependencies><dependency><groupId>test</groupId><artifactId>a</artifactId><version>2.4</version><scope>runtime</scope></dependency><dependency><groupId>test</groupId><artifactId>a</artifactId><version>2.5</version><scope>compile</scope></dependency><dependency><groupId>test</groupId><artifactId>c</artifactId><version>2.1</version><scope>compile</scope></dependency><dependency><groupId>test</groupId><artifactId>d</artifactId><version>1.0</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>test</groupId><artifactId>a</artifactId><version>3.6</version><scope>runtime</scope></dependency><dependency><groupId>test</groupId><artifactId>a</artifactId><version>3.7</version><scope>runtime</scope></dependency><dependency><groupId>test</groupId><artifactId>c</artifactId><scope>runtime</scope></dependency></dependencies>
</project>

Project C POM:

<project><parent><artifactId>A</artifactId><groupId>maven</groupId><version>1.0</version></parent><modelVersion>4.0.0</modelVersion><groupId>maven</groupId><artifactId>B</artifactId><packaging>pom</packaging><name>B</name><version>1.0</version><dependencyManagement><dependencies><dependency><groupId>test</groupId><artifactId>d</artifactId><version>1.0</version></dependency><dependency><groupId>test</groupId><artifactId>a</artifactId><version>2.3</version><scope>runtime</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>test</groupId><artifactId>a</artifactId><version>3.0</version><scope>runtime</scope></dependency><dependency><groupId>test</groupId><artifactId>c</artifactId><scope>runtime</scope></dependency></dependencies>
</project>

1.配置优先级

1.1 不同节点优先级

同一个Pom中dependencies优先级高于dependencyManagement。比如在Project C中dependencies中定义了依赖a的版本和scope,所以会忽略dependencyManagement中依赖a的版本和scope。依赖a会采用版本3.0。

1.2 不同pom中dependencyManagement优先级

父子Pom中子Pom中dependencyManagement优先级高于父Pom中的dependencyManagement。比如在Project B中dependencies中没有定义了依赖c的版本,并且dependencyManagement定义了依赖c版本为2.1, 所以会忽略ProjectA中dependencyManagement中依赖c的版本和scope。

1.3 重复定义优先级

dependency在同一个dependencies或dependencyManagement中,如果出现相同{groupId,artifactId},则后出现的会覆盖先出现。
比如在Project B中dependencies和dependencyManagement中重复定义了依赖a,所以在各自节点中,起作用的都是后定义的版本。按照目前Pom,依赖a会是3.7,如果删除dependencies对依赖a的定义,则依赖a会采用版本2.5。

2.版本确定流程

2.1 对Pom的dependencies中定义的{groupId,artifactId}组合顺序遍历,如果有相同组合以后定义的信息为主。
2.2 针对以上去重后每个租户,如果version和exclusions都明确定义了,则确定了此依赖。
2.3 如果version和exclusions有一个没有定义,如果本Pom中dependencyManagement中定义了此依赖信息,则进行加载此依赖。
2.4 如果本Pom中dependencyManagement中没有定义了此依赖,则去父Pom的dependencyManagement中查找此依赖定义,如果没有会一直查找到默认Pom文件对应节点
2.5 将依赖的Pom的dependencies按照 2.1进行处理后,还需要去掉exclusions的租户后跳转到2.2。
2.6 如果加载完成所有依赖,出现了相同{groupId,artifactId},则需要根据仲裁机制的路径优先和声明优先进行选择。
在这里插入图片描述

3.版本冲突

maven有完善的版本管理和冲突解决机制,但是为什么实际工程中还会出现依赖多版本和冲突问题。主要是因为maven的冲突仲裁机制是以Pom为单位进行的,如果一个项目有很大module,而对依赖版没有进行统一管理,就会出现相同的依赖在不同module的版本不一致问题。
例如上边的例子依赖a在Project B中引入的是2.5版本,在Project C中引入的是3.0版本。所以针对整个项目在运行过程中可能会优先加载2.5版本,这就导致Project C中调用依赖3.0版本a的一些方法缺失或者实现有差异,导致功能异常。

3.1 版本统一管理

将所有版本管理统一到父Pom的dependencyManagement中,子项目中不要配置dependencyManagement。

3.2 排除依赖

可以在子项目定义依赖时通过exclusions将一些间接依赖排除掉,解决版本冲突。

总结

主要是对maven依赖传递机制的介绍、依赖管理的的配置、依赖版本和范围确定机制、依赖冲突解决等。

参考

1.maven文档-依赖机制

相关文章:

  • 【大数据分布并行处理】单元测试(三)
  • CMOS介绍
  • [HXPCTF 2021]includer‘s revenge
  • MYSQL---基础篇
  • 4.HTML网页开发的工具
  • Clickhouse学习笔记(11)—— 数据一致性
  • ELK分布式日志
  • TypeScript: 判断两个数组的内容是否相等
  • 解决游戏找不到x3daudio1_7.dll文件的5个方法,快速修复dll问题
  • Ubuntu 20.04编译Chrome浏览器
  • 『MySQL快速上手』-⑤-数据类型
  • 使用微信小程序控制蓝牙小车(微信小程序端)
  • U盘插在电脑上显示要格式化磁盘怎么办
  • 华为开源carbondata中的使用问题处理
  • gma 2.0.3 (2023.11.12) 更新日志
  • 【Amaple教程】5. 插件
  • HTML-表单
  • Javascript设计模式学习之Observer(观察者)模式
  • JS数组方法汇总
  • tensorflow学习笔记3——MNIST应用篇
  • Three.js 再探 - 写一个跳一跳极简版游戏
  • 工作手记之html2canvas使用概述
  • 基于Mobx的多页面小程序的全局共享状态管理实践
  • 简单基于spring的redis配置(单机和集群模式)
  • 聚簇索引和非聚簇索引
  • 利用jquery编写加法运算验证码
  • 区块链分支循环
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 源码安装memcached和php memcache扩展
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • ​业务双活的数据切换思路设计(下)
  • #ubuntu# #git# repository git config --global --add safe.directory
  • #宝哥教你#查看jquery绑定的事件函数
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • (MATLAB)第五章-矩阵运算
  • (ZT)出版业改革:该死的死,该生的生
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (转)Sql Server 保留几位小数的两种做法
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .cfg\.dat\.mak(持续补充)
  • .gitignore文件---让git自动忽略指定文件
  • .NET Standard 的管理策略
  • .NET序列化 serializable,反序列化
  • /etc/shadow字段详解
  • @RequestBody的使用
  • [Android]使用Git将项目提交到GitHub
  • [C#]扩展方法
  • [JS]JavaScript 注释 输入输出语句
  • [JS入门到进阶] 7条关于 async await 的使用口诀,新学 async await?背10遍,以后要考!快收藏
  • [k8s系列]:kubernetes·概念入门
  • [Linux]文件基础-如何管理文件
  • [nginx] 网上最全面nginx教程(近100篇文章整理)
  • [NOIP2000] 乘积最大