赞
踩
Maven 依赖传递是 Maven 的核心机制之一,它能够一定程度上简化 Maven 的依赖配置。
如下图所示,项目 A 依赖于项目 B,B 又依赖于项目 C,此时 B 是 A 的直接依赖,C 是 A 的间接依赖。
Maven 的依赖传递机制是指:不管 Maven 项目存在多少间接依赖,POM 中都只需要定义其直接依赖,不必定义任何间接依赖,Maven 会自动读取当前项目各个直接依赖的 POM,将那些必要的间接依赖以传递性依赖的形式引入到当前项目中。Maven 的依赖传递机制能够帮助用户一定程度上简化 POM 的配置。
基于 A、B、C 三者的依赖关系,根据 Maven 的依赖传递机制,我们只需要在项目 A 的 POM 中定义其直接依赖 B,在项目 B 的 POM 中定义其直接依赖 C,Maven 会解析 A 的直接依赖 B的 POM ,将间接依赖 C 以传递性依赖的形式引入到项目 A 中。
通过这种依赖传递关系,可以使依赖关系树迅速增长到一个很大的量级,很有可能会出现依赖重复,依赖冲突等情况,Maven 针对这些情况提供了如下功能进行处理。
首先,我们要知道 Maven 在对项目进行编译、测试和运行时,会分别使用三套不同的 classpath。Maven 项目构建时,在不同阶段引入到 classpath 中的依赖时不同的。
我们可以在 POM 的依赖声明使用 scope 元素来控制依赖与三种 classpath(编译 classpath、测试 classpath、运行 classpath )之间的关系,这就是依赖范围。
下面我们首先要对三种classpath进行了解:
可选配置有 compile、test、provided、runtime、system、import,若不指定则默认 compile。 首先我们要知道classes和test-classes目录是不存jar包的,项目所依赖的jar包直接使用的是本地maven仓库的,依赖范围更为通俗的理解,其实就是给依赖包打标记,例如将 A 依赖包标记为“compile”,Maven 就知道 A 依赖包不仅运行classes目录的代码要用,运行test-classes也要用,打成的war包也要用(打包就是需要把依赖的包也打包到war,这样war就可以不依赖于本地仓库而放到tomcat当中进行运行了)。
对于编译、测试、运行三种classpath 都有效
。典型的例子是spring-core,在编译、测试和运行的时候都需要使用该依赖。只对于测试classpath有效
,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子是junit,它只有在编译测试代码及运行测试的时候才需要。对于编译和测试classpath有效
,但在运行时无效。典型的例子是 servlet-api,,编译和测试项目的时候需要该依赖,假如需要打包成war然后放到tomcat当中运行,由于tomcat已经提供,就不需要Maven重复地引入一遍。所以将 scope 设置为 provided 的依赖不会参与项目的war打包
。假如打包为jar,设置与不设置provided并不会影响maven将依赖打包到jar当中
。说到provided,这里就要说到
<dependency>
下的子标签<optional>
,这两者的区别在于:
1、<optional>
为true 表示依赖不会传递。例如:x依赖B,B又依赖于A(x->B->A),则A中设置<optional>
为true的依赖不会被传递到x中。假如当前项目某个依赖<optional>
为true 并不会影响该依赖在当前项目的打包,他只会影响所依赖当前项目的其他项目打包。
2、<scope>
为provided 代表的是该依赖不参与打war包。
在目标容器中已经提供了这个依赖,无需在提供
关于optional的详解:https://blog.csdn.net/weixin_43888891/article/details/130510971
依赖范围与三种 classpath 的关系一览表,如下所示。
项目 A 依赖于项目 B,B 又依赖于项目 C,此时我们可以将 A 对于 B 的依赖称之为第一直接依赖,B 对于 C 的依赖称之为第二直接依赖。
B 是 A 的直接依赖,C 是 A 的间接依赖,根据 Maven 的依赖传递机制,间接依赖 C 会以传递性依赖的形式引入到 A 中,但这种引入并不是无条件的,它会受到依赖范围的影响。
传递性依赖的依赖范围受第一直接依赖和第二直接依赖的范围影响,如下表所示。
注:上表中,左边第一列表示第一直接依赖的依赖范围,上边第一行表示第二直接依赖的依赖范围。交叉部分的单元格的取值为传递性依赖的依赖范围,若交叉单元格取值为“-”,则表示该传递性依赖不能被传递。
通过上表,可以总结出以下规律:
Maven 的依赖传递机制可以简化依赖的声明,用户只需要关心项目的直接依赖,而不必关心这些直接依赖会引入哪些间接依赖。但当一个间接依赖存在多条引入路径时,为了避免出现依赖重复的问题,Maven 通过依赖调节来确定间接依赖的引入路径。
依赖调节遵循以下两条原则:
以上两条原则,优先使用第一条原则解决,第一条原则无法解决,再使用第二条原则解决。
引入路径短者优先
引入路径短者优先,顾名思义,当一个间接依赖存在多条引入路径时,引入路径短的会被解析使用。
例如,A 存在这样的依赖关系:
D 是 A 的间接依赖,但两条引入路径上有两个不同的版本,很显然不能同时引入,否则造成重复依赖的问题。根据 Maven 依赖调节的第一个原则:引入路径短者优先,D(1.0)的路径长度为 3,D(2.0)的路径长度为 2,因此间接依赖 D(2.0)将从 A->X->D(2.0) 路径引入到 A 中。
先声明者优先
先声明者优先,顾名思义,在引入路径长度相同的前提下,POM 文件中依赖声明的顺序决定了间接依赖会不会被解析使用,顺序靠前的优先使用。
例如,A 存在以下依赖关系:
D 是 A 的间接依赖,其两条引入路径的长度都是 2,此时 Maven 依赖调节的第一原则已经无法解决,需要使用第二原则:先声明者优先。
A 的 POM 文件中配置如下。
<dependencies>
...
<dependency>
...
<artifactId>B</artifactId>
...
</dependency>
...
<dependency>
...
<artifactId>X</artifactId>
...
</dependency>
...
</dependencies>
有以上配置可以看出,由于 B 的依赖声明比 X 靠前,所以间接依赖 D(1.0)将从 A->B->D(1.0) 路径引入到 A 中。
可选依赖就是指的optional标签,关于optional的详解:https://blog.csdn.net/weixin_43888891/article/details/130510971
传递性依赖可以帮助我们简化项目依赖的管理,但是同时也会带来其他的不必要的风险,例如:会隐式地引入一些依赖,这些依赖可能并不是我们希望引入的,或者这些隐式引入的依赖是 SNAPSHOT 版本的依赖。依赖的不稳定导致了我们项目的不稳定。
在我们的项目中,spring-boot-starter-test 依赖中排除了 junit-vintage-engine 依赖是由于我们使用的 springboot 版本是 2.2.6-RELEASE,对应的 Junit 版本是 5.x,但 junit-vintage-engine 依赖中包含了 4.x 版本的 Junit,此时我们就可以将该依赖排除。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
关于 exclusions 元素及排除依赖说明如下:
在我们实际的开发过程中,我们可能会需要整合很多第三方框架,在整合这些框架的时候,往往需要在 pom.xml 里面添加多个依赖来完成整合。而这些依赖往往是需要保持相同版本的,在升级框架的时候,都是要统一升级到一个相同的版本。
如下图,我们可以看到,在引入 dubbo 框架的时候,我们需要引入两个相关的依赖,而且版本号是相同的,这个时候,我们就可以把对应的版本号提取出来,放到 properties 标签里面,作为一个全局参数来使用。类似于 Java 语言中抽象的思想。
这时候,我们可以看到,如果在将来的某一天我们需要升级升级 dubbo 框架对应的版本,只需要修改 properties 中的版本号,就能将所有依赖的版本一起升级。
依赖管理就是指的dependencyManagement标签,直接看这一篇文章即可:https://blog.csdn.net/weixin_43888891/article/details/130520345
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。