Maven实战.依赖(依赖范围、传递性依赖、依赖调解、可选依赖等)
文章目录
- 依赖的配置
- 依赖范围
- 传递性依赖
- 传递性依赖和依赖范围
- 依赖调解
- 可选依赖
- 最佳实践
- 排除依赖
- 归类依赖
- 优化依赖
依赖的配置
依赖会有基本的groupId、artifactld 和 version等元素组成。其实一个依赖声明可以包含如下的一些元素:
<project>
...<dependencies><dependency><groupId>...</groupId><artifactId>...</artifactId><version>...</version><type>...</type><scope>...</scope><optional>...</optional><exclusions><exclusion>...</exclusion>...</exclusions></dependency>...</dependencies>
...
</project>
其中每个标签的意思,直接参考Maven实战.pom.xml标签说明中的<dependencies>处。
依赖范围
参考Maven实战.pom.xml标签说明中的<scope>处。
传递性依赖
考虑一个基于 Spring Framework 的项目,如果不使用 Maven,那么在项目中就需要手动下载相关依赖。由于 Spring Framework 又会依赖于其他开源类库,因此实际中往往会下载一个很大的如 spring-framework-2.5.6-with-dependencies.zip 的包,这里包含了所有 SpringFramework 的 jar 包,以及所有它依赖的其他 jar 包。这么做往往就引人了很多不必要的依赖。另一种做法是只下载 spring-framework-2.5.6.zip 这样一个包,这里不包含其他相关依赖,到实际使用的时候,再根据出错信息,或者查询相关文档,加入需要的其他依赖。很显然,这也是一件非常麻烦的事情。
Maven 的传递性依赖机制可以很好地解决这一问题。假如有一个项目 account-email,该项目有一个 org.springframework : spring-core:2.5.6 的依赖,而实际上 spring-core 也有它自己的依赖,我们可以直接访问位于中央仓库的该构件的 POM,也就是 spring-core-2.5.6.pom。该文件包含了一个 commons-logging 依赖,如下。
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.1</version><scope>compile</scope>
</dependency>
该依赖的 scope 是 compile(或者没有声明依赖范围则依赖范围就是默认的 compie)。同时项目 account-email 中,spring-core 的依赖范围我们也设置为 compile。那么,此时 commons-logging 就会成为 account-email 的 compile 范围依赖,commons-logging 是 account-email 的一个传递性依赖。如下图

有了传递性依赖机制,在使用 SpringFramework 的时候就不用去考虑它依赖了什么,也不用担心引人多余的依赖。Maven 会解析各个直接依赖的 POM,将那些必要的间接依赖以传递性依赖的形式引人到当前的项目中。
传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种 classpah 的关系,还对传递性依赖产生影响。上面的例子中,account-email 对于 spring-core 的依赖范围是 compile,spring-core 对于 commons-logging 的依赖范围是 compile,那么 account-email 对于 commons-logging 这一传递性依赖的范围也就是 compile。
假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如下图所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。

为了能够更好地理解,这里再举个例子。account-email 项目有一个com.icegreen : greenmail : 1.3.1b 的直接依赖,我们说这是第一直接依赖,其依赖范围是 test,而 greenmail 又有一个 javax.mail : mail : 1.4 的直接依赖,我们说这是第二直接依赖,其依赖范围是 compile。显然javax.mail : mail : 1.4 是 account-email 的传递性依赖,对照上图可以知道,当第一直接依赖范围为 test,第二直接依赖范围是 compile 的时候,传递性依赖的范围是 test,因此 javax.mail : mail : 1.4 是 account-email 的一个范围是 test 的传递性依赖。
仔细观察一下上图,可以发现这样的规律:
- 当第二直接依赖的范围是 compile 的时候,传递性依赖的范围与第一直接依赖的范围一致
- 当第二直接依赖的范围是 test 的时候依赖不会得以传递
- 当第二直接依赖的范围是 provided的时候,只传递第一直接依赖范围也为 provided 的依赖,且传递性依赖的范围同样为 provided;
- 当第二直接依赖的范围是 runtime 的时候,传递性依赖的范围与第一直接依赖的范围一致,但 compile 例外,此时传递性依赖的范围为runtime。
依赖调解
Maven 引人的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清楚地知道该传递性依赖是从哪条依赖路径引入的。
例如,项目 A 有这样的依赖关系:A -> B -> C -> X(1.0)、A -> D -> X(2.0),X 是 A 的传递性依赖,但是两条依赖路径上有两个版本的 X,那么哪个 X 会被 Maven 解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。
Maven依赖调解(Dependency Mediation)的第一原则是:路径最近者优先。该例中 X(1.0) 的路径长度为3,而 X(2.0) 的路径长度为2,因此 X(2.0) 会被解析使用。
依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A -> B -> Y(1.0)、A -> C -> Y(2.0),Y(1.0) 和 Y(2.0) 的依赖路径长度是一样的,都为2。那么到底谁会被解析使用呢?
在 Maven 2.0.8 及之前的版本中,这是不确定的,但是从 Maven2.0.9 开始,为了尽可能避免构建的不确定性,Maven 定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在 POM 中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。该例中,如果 B 的依赖声明在 C 之前,那么 Y(1.0) 就会被解析使用。
可选依赖
假设有这样一个依赖关系,项目 A 依赖于项目 B,项目 B 依赖于项目 X 和 Y,B 对于 X 和 Y 的依赖都是可选依赖:A -> B、B -> X(可选)、B -> Y(可选)。根据传递性依赖的定义,如果所有这三个(A 对 B 的依赖、B 对 X 的依赖 以及 B 对 Y 的依赖)依赖的范围都是 compile,那么 X、Y 就是 A 的 compile 范围传递性依赖。然而,由于这里 X、Y 是可选依赖,依赖将不会得以传递。换句话说,X、Y 将不会对 A 有任何影响,如下图所示。

为什么要使用可选依赖这一特性呢?可能项目 B 实现了两个特性,其中的特性一依赖于 X,特性二依赖于 Y,而且这两个特性是互斥的,用户不可能同时使用两个特性。比如 B 是一个持久层隔离工具包,它支持多种数据库,包括 MSQL、PostgreSQL等。在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。
项目 B 的依赖声明如下:
<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.10</version><optional>true</optional></dependency><dependency><groupId>postgresql</groupId><artifactId>postgresql</artifactId><version>8.4-701.jdbc3</version><optional>true</optional></dependency>
</dependencies>
上述 XML 代码片段中,使用<optional>元素表示 mysql-connector-java和 postgresql 这两个依赖为可选依赖,它们只会对当前项目 B 产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此,当项目 A 依赖于项目 B 的时候,如果其实际使用基于MySOL数据库,那么在项目 A 中就需要自己显式地声明 mysql-connector-java 这一依赖。
最后,关于可选依赖需要说明的一点是,在理想的情况下,是不应该使用可选依赖的。前面我们可以看到,使用可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能。这个原则在规划 Maven 项目的时候也同样适用。在上面的例子中,更好的做法是为MYSQL 和 PostgreSQL 分别创建一个 Maven 项目,基于同样的groupld 分配不同的 artifactld,在各自的 POM 中声明对应的JDBC驱动依赖,而且不使用可选依赖,用户则根据需要选择使用哪个驱动的 Maven 项目的构件。由于传递性依赖的作用,就不用再声明 JDBC 驱动依赖。
最佳实践
Maven 依赖涉及的知识点比较多,在理解了主要的功能和原理之后,最需要的当然就是经验总结了,这里归纳了一些使用 Maven 依赖常见的技巧,方便用来避免和处理很多常见的问题。
排除依赖
传递性依赖会给项目隐式地引入很多依赖,这极大地简化了项目依赖的管理,但是有些时候这种特性也会带来问题。例如,当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的 SNAPSHOT 版本,那么这个 SNAPSHOT 就会成为当前项目的传递性依赖,而 SNAPSHOT 的不稳定性会直接影响到当前的项目。这时就需要排除掉该 SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。还有一些情况,你可能也想要替换某个传递性依赖,比如 Sun JTA API,Hibernate 依赖于这个 JAR,但是由于版权的因素,该类库不在中央仓库中,而 Apache Geronimo 项目有一个对应的实现。这时你就可以排除 Sun JAT API,再声明 Geronimo 的 JTA API 实现,示例代码如下:
<dependencies><dependency><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-b</artifactId><version>1.0.0</version><exclusions><exclusion><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-c</artifactId></exclusion></exclusions></dependency><dependency><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-c</artifactId><version>1.0.0</version></dependency>
</dependencies>
上述代码中,当前项目 A 依赖于项目 B,但是由于一些原因,不想引入传递性依赖 C 而是自己显式地声明对于项目 C 1.1.0 版本的依赖。代码中使用 exclusions 元素声明排除依赖,exclusions 可以包含一个或者多个 exclusion 子元素,因此可以排除一个或者多个传递性依赖。需要注意的是,声明 exclusion 的时候只需要 groupId 和 artifactld,而不需要 version 元素,这是因为只需要 groupId 和 artifactld 就能唯一定位依赖图中的某个依赖。换句话说,Maven 解析后的依赖中,不可能出现 groupId 和 artifactld 相同,但是 version 不同的两个依赖

归类依赖
简单说就是一批依赖来自同一项目(groupId)的不同模块(artifactId),但是版本号相同,此时可以不用为每个依赖声明一个 version,而是统一定义版本号,然后每个依赖引用这个版本号即可。
<properties><myproject.version>1.0.0</myproject.version>
</properties><dependencies><dependency><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-b</artifactId><version>${myproject.version}</version></dependency><dependency><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-c</artifactId><version>${myproject.version}</version></dependency>
</dependencies>
这里简单用到了Maven属性,首先使用 <properties > 元素定义 Maven 属性,该例中定义了一个 myproject.version 子元素,其值为1.0.0。有了这个属性定义之后,Mave n运行的时候会将POM 中的所有的 ${myproject.version} 替换成实际值1.0.0。也就是说,可以使用美元符号和大括弧环绕的方式引用 Maven 属性。然后,将所有统一依赖的版本值用这一属性引用表示。
优化依赖
在软件开发过程中,程序员会通过重构等方式不断地优化自己的代码,使其变得更简洁、更灵活。同理,程序员也应该能够对 Maven 项目的依赖了然于胸,并对其进行优化如去除多余的依赖,显式地声明某些必要的依赖。
Maven 会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖(Resolved Dependency)。可以运行如下的命令查看当前项目的已解析依赖:
mvn dependency:list
在你的项目中执行该命令,结果会显示项目的所有的已解析依赖,同时,每个依赖的范围也得以明确标示。在此基础上,还能进一步了解已解析依赖的信息。
将直接在当前项目 POM 声明的依赖定义为顶层依赖,而这些顶层依赖的依赖则定义为第二层依赖,以此类推,有第三、第四层依赖。当这些依赖经 Maven 解析后,就会构成一个依赖树,通过这棵依赖树就能很清楚地看到某个依赖是通过哪条传递路径引入的。可以运行如下命令查看当前项目的依赖树:
mvn dependency:tree
通过结果你会发现,有的依赖没有声明,但是通过别的依赖传递了进来,也能看见每个依赖的范围。
使用 dependency:list 和 dependency:tree 可以帮助我们详细了解项目中所有依赖的具体信息,在此基础上,还有dependency:analyze 工具可以帮助分析当前项目的依赖。
mvn dependency:analyze
该结果中重要的是两个部分。首先是 Used umdeclared dependencies,意指项目中使用到的,但是没有显式声明的依赖,这种依赖意味着潜在的风险,当前项目可能在使用它们,例如有很多相关的 Java import 声明,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。例如由于接口的改变,当前项目中的相关代码无法编译。这种隐藏的、潜在的威胁一旦出现,就往往需要耗费大量的时间来查明真相。因此,最好显式声明任何项目中直接用到的依赖。
结果中还有一个重要的部分是 Unused declared dependeneies,意指项目中未使用的,但显式声明的依赖,需要注意的是,对于这样一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。由于 dependency:analyze 只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。
相关文章:
Maven实战.依赖(依赖范围、传递性依赖、依赖调解、可选依赖等)
文章目录 依赖的配置依赖范围传递性依赖传递性依赖和依赖范围依赖调解可选依赖最佳实践排除依赖归类依赖优化依赖 依赖的配置 依赖会有基本的groupId、artifactld 和 version等元素组成。其实一个依赖声明可以包含如下的一些元素: <project> ...<depende…...
关于React17的setState
不可变值 state必须在构造函数中定义 在setState之前不能修改state的值,不要直接修改state,使用不可变值 可能是异步更新 直接使用时异步的 this.setState({count: this.state.count 1 }, () > {console.log(count by callback, this.state.count) // 回调函…...
2024华为OD机试真题-英文输入法Python-C卷D卷-100分
2024华为OD机试题库-(C卷+D卷)-(JAVA、Python、C++) 题目描述 主管期望你来实现英文输入法单词联想功能,需求如下: 依据用户输入的单词前缀,从已输入的英文语句中联想出用户想输入的单词。 按字典序输出联想到的单词序列,如果联想不到,请输出用户输入的单词前缀。 注意 英…...
magento2 安装win环境和linux环境
win10 安装 安装前提,php,mysql,apach 或nginx 提前安装好 并且要php配置文件里,php.ini 把错误打开 display_errorsOn开始安装 检查环境 填写数据库信息 和ssl信息,如果ssl信息没有,则可以忽略 填写域名和后台地址࿰…...
【城市数据集】世界城市数据库和访问门户工具WUDAPT
世界城市数据库和访问门户工具WUDAPT WUDAPTLCZ分类具体步骤参考 在 城市气候研究中,用于描述城市特征的数据集一般采用基于类别的传统方法,将城市地区分为数量有限的类型,从而导致精确度下降。越来越多的新数据集以亚米微尺分辨率描述城市的…...
网络爬虫必备工具:代理IP科普指南
文章目录 1. 网络爬虫简介1.1 什么是网络爬虫?1.2 网络爬虫的应用领域1.3 网络爬虫面临的主要挑战 2. 代理IP:爬虫的得力助手2.1 代理IP的定义和工作原理2.2 爬虫使用代理IP的必要性 3. 代理IP的类型及其在爬虫中的应用3.1 动态住宅代理3.2 动态数据中心…...
JMeter接口测试-5.JMeter高级使用
JMeter高级使用 案例: 用户登录后-选择商品-添加购物车-创建订单-验证结果 问题: JMeter测试中,验证结果使用断言,但断言都是固定的内容假如要判断的内容(预期内容)是在变化的, 有时候还是不确定的, 那该怎么办呢? 解决&…...
网络安全大模型开源项目有哪些?
01 Ret2GPT 它是面向CTF二进制安全的工具,结合ChatGPT API、Retdec和Langchain进行漏洞挖掘,它能通过问答或预设Prompt对二进制文件进行分析。 https://github.com/DDizzzy79/Ret2GPT 02 OpenAI Codex 它是基于GPT-3.5-turbo模型,用于编写…...
【赠书第18期】人工智能B2B落地实战:基于云和Python的商用解决方案
文章目录 前言 1 方案概述 2 方案实施 2.1 云平台选择 2.2 Python环境搭建 2.3 应用开发与部署 2.4 应用管理 2.5 安全性与隐私保护 3 方案优势与效益 4 推荐图书 5 粉丝福利 前言 随着云计算技术的快速发展,越来越多的企业开始将业务迁移至云端&#x…...
《昇思25天学习打卡营第24天》
接续上一天的学习任务,我们要继续进行下一步的操作 构造网络 当处理完数据后,就可以来进行网络的搭建了。按照DCGAN论文中的描述,所有模型权重均应从mean为0,sigma为0.02的正态分布中随机初始化。 接下来了解一下其他内容 生成…...
KeePass密码管理工具部署
KeePass密码管理工具部署 安装包下载入口 双击执行,根据提示完成安装: 安装完成后如图:...
C#中导出dataGridView数据为Excel
C#中导出dataGridView数据为Excel #region 导出Excel功能函数 /// <summary> /// dataGridView 导出Excel功能函数 /// </summary> /// <param name"dataView">dataGridView数据表</param> /// <param name"filePath">路径…...
算法学习6——贪心算法
什么是贪心算法? 贪心算法是一种在每一步选择中都采取当前状态下最优或最有利的选择的算法。其核心思想是通过一系列局部最优选择来达到全局最优解。贪心算法广泛应用于各种优化问题,如最短路径、最小生成树、背包问题等。 贪心算法的特点 局部最优选…...
【C++】标准库:介绍string类
string 一.string类介绍二.string类的静态成员变量三.string类的常用接口1.构造函数(constructor)2.析构函数(destructor)3.运算符重载(operator)1.operator2.operator[]3.operator4.operator 4.string的四…...
未来不会使用 AI 的人真的会被淘汰吗?
AI 是今年大火的一个话题,随着 ChatGPT 之类的一系列大模型开始流行以后,有不少的培训机构宣称这样的口号: “未来不会使用 AI 的人将会被淘汰”。我觉得这个观点本身并没有错,但是关键在于那些培训机构出于自身的利益,故意忽略了…...
K8S及Rancher部署
前言 这篇文写的有点子啰嗦,甚至为了控制篇幅我还分出了其他好几篇文章,只在本文中保留了我认为必须存在。而之所以篇幅这么长,一方面是我在相关领域完全新手,啥啥都不会;而另一方面是我所参考的资料都过于精简&#…...
Qt Creator使用git管理代码
1.在GitHub中新建仓库,设置好仓库名后,其它的设置默认即可。 2.打开git bash,输入以下命令: git config --global user.name "xxxxx" #设置你的GitHub用户名 git config --global user.email "xxxxxxxxx.…...
pandas教程:pandas读取csv文件并指定字段数据类型
文章目录 pandas指定数据类型处理数据类型错误parse_dates参数pandas数据类型处理示例pandas指定数据类型 在读取csv文件时,我们可以使用dtype参数来指定每个列的数据类型。这个参数接受一个字典类型的值,其中键是列名,值是数据类型。数据类型可以是Pandas类型或NumPy类型,…...
c#中使用数据验证器
前言 在很多情况下,用户的输入不一定满足我们的设计要求,需要验证输入是否正确,传统的方案是拿到控件数据进行逻辑判定验证后,给用户弹窗提示。这种方法有点职责延后的感觉,数据视图层应该很好的处理用户的输入。使用…...
Java真人版猫爪老鼠活动报名平台系统
🐾“真人版猫爪老鼠活动报名平台系统”——趣味追逐,等你来战!🐭 🐱【萌宠变主角,现实版趣味游戏】 厌倦了电子屏幕的虚拟游戏?来试试“真人版猫爪老鼠活动”吧!在这个平台上&…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
