Skip to content

maven依赖传递性

依赖传递性

在项目往往需要引入多个依赖, 而引入的依赖又会引入其他的依赖。

例如: 项目中引入了spring-core依赖, 而spring-core又依赖commons-logging。因为项目直接引入了spring-core依赖,则spring-core为项目的第一直接依赖, 而因为spring-core引入了commons-logging依赖, 则commons-logging为项目的一个传递性依赖。Maven会解析各个直接依赖的POM, 将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。

传递性依赖和依赖范围

依赖范围不仅可以控制依赖和三种classpath的关系,还对传递性依赖产生影响。

假设A依赖B, B依赖C, 我们说A对于B是第一直接依赖, B对C是第二直接依赖, A对于C是传递性依赖。 第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。 如下图: 最左边一列表示第一直接依赖方位, 最上面一行表示第二直接依赖范围, 中间交叉单元格则表示传递性依赖的范围。

 compiletestprovidedruntime
compilecompile------runtime
testtest------test
providedprovided---providedprovided
runtimeruntime------runtime

当第二直接依赖的范围是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依赖调解的第一原则是:路径最近者优先。该例中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,。 那么到底谁会被解析使用呢?在Maven2.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(可选)。 根据传递性依赖的定义,如果所有这三个依赖的范围都是compile,那么X, Y就是A的compile范围传递性依赖。然而,由于这里X,Y是可选依赖,依赖将不会得以传递。换句话说,X,Y将不会对A有任何影响。

为什么要使用可选依赖这一特性呢?可能项目B实现了两个特性,其中的特性一依赖于X,特性而依赖于Y,而且两个特性时互斥的,用户不能同时使用两个特性。比如B是一个持久层隔离工具包,它支持多种数据库,报错MySQL, 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.jgbc3</version>
      <optional>true</optional>
   </dependency>
</dependencies>

上述代码片段中, 使用<optional>元素表示mysql-connector-java和postgresql这两个依赖为可选依赖,它们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此,当项目A依赖于项目B的时候,如果其实际使用基于MySQL数据,那么项目A中就需要显示的声明mysql-connetor-java这一依赖,如下:

<dependencies>
   <dependency>
      <groupId>org.rogueq.mvnbook</groupId>
      <artifactId>project-b</artifactId>
      <version>1.0.0</version>
   </dependency>
   <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.10</version>
   </dependency>
</dependencies>

Comments