依赖冲突
1、对于 Maven 而言,同一个 groupId 同一个 artifactId 下,只能使用一个 version。
<dependency> <groupId>in.hocg.boot</groupId> <artifactId>mybatis-plus-spring-boot-starter</artifactId> <version>1.0.48</version> </dependency> <!-- 只会使用 1.0.49 这个版本的依赖 --> <dependency> <groupId>in.hocg.boot</groupId> <artifactId>mybatis-plus-spring-boot-starter</artifactId> <version>1.0.49</version> </dependency>
若相同类型但版本不同的依赖存在于同一个 pom 文件,只会引入后一个声明的依赖。
2、项目的两个依赖同时引入了某个依赖。
举个例子,项目存在下面这样的依赖关系:
依赖链路一:A -> B -> C -> X(1.0) 依赖链路二:A -> D -> X(2.0)
这两条依赖路径上有两个版本的 X,为了避免依赖重复,Maven 只会选择其中的一个进行解析。
哪个版本的 X 会被 Maven 解析使用呢?
Maven 在遇到这种问题的时候,会遵循 路径最短优先 和 声明顺序优先 两大原则。解决这个问题的过程也被称为 Maven 依赖调解 。
路径最短优先
依赖链路一:A -> B -> C -> X(1.0) // dist = 3 依赖链路二:A -> D -> X(2.0) // dist = 2
依赖链路二的路径最短,因此,X(2.0)会被解析使用。
不过,你也可以发现。路径最短优先原则并不是通用的,像下面这种路径长度相等的情况就不能单单通过其解决了:
依赖链路一:A -> B -> X(1.0) // dist = 3 依赖链路二:A -> D -> X(2.0) // dist = 2
因此,Maven 又定义了声明顺序优先原则。
依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A->B->Y(1.0)、A-> C->Y(2.0),Y(1.0)和 Y(2.0)的依赖路径长度是一样的,都为 2。Maven 定义了依赖调解的第二原则:
声明顺序优先
在依赖路径长度相等的前提下,在 pom.xml
中依赖声明的顺序决定了谁会被解析使用,顺序最前的那个依赖优胜。该例中,如果 B 的依赖声明在 D 之前,那么 X (1.0)就会被解析使用。
<!-- A pom.xml --> <dependencies> ... dependency B ... dependency D </dependencies>
# 排除依赖
单纯依赖 Maven 来进行依赖调解,在很多情况下是不适用的,需要我们手动排除依赖。
举个例子,当前项目存在下面这样的依赖关系:
依赖链路一:A -> B -> C -> X(1.5) // dist = 3 依赖链路二:A -> D -> X(1.0) // dist = 2
根据路径最短优先原则,X(1.0) 会被解析使用,也就是说实际用的是 1.0 版本的 X。
但是!!!这会一些问题:如果 D 依赖用到了 1.5 版本的 X 中才有的一个类,运行项目就会报NoClassDefFoundError
错误。如果 D 依赖用到了 1.5 版本的 X 中才有的一个方法,运行项目就会报NoSuchMethodError
错误。
现在知道为什么你的 Maven 项目总是会报NoClassDefFoundError
和NoSuchMethodError
错误了吧?
如何解决呢? 我们可以通过exclusion
标签手动将 X(1.0) 给排除。
<dependency> ...... <exclusions> <exclusion> <artifactId>x</artifactId> <groupId>org.apache.x</groupId> </exclusion> </exclusions> </dependency>
一般我们在解决依赖冲突的时候,都会优先保留版本较高的。这是因为大部分 jar 在升级的时候都会做到向下兼容。
如果高版本修改了低版本的一些类或者方法的话,这个时候就能直接保留高版本了,而是应该考虑优化上层依赖,比如升级上层依赖的版本。
还是上面的例子:
依赖链路一:A -> B -> C -> X(1.5) // dist = 3 依赖链路二:A -> D -> X(1.0) // dist = 2
我们保留了 1.5 版本的 X,但是这个版本的 X 删除了 1.0 版本中的某些类。这个时候,我们可以考虑升级 D 的版本到一个 X 兼容的版本。
# Maven 仓库
在 Maven 世界中,任何一个依赖、插件或者项目构建的输出,都可以称为 构件 。
坐标和依赖是构件在 Maven 世界中的逻辑表示方式,构件的物理表示方式是文件,Maven 通过仓库来统一管理这些文件。 任何一个构件都有一组坐标唯一标识。有了仓库之后,无需手动引入构件,我们直接给定构件的坐标即可在 Maven 仓库中找到该构件。
Maven 仓库分为:
- 本地仓库:运行 Maven 的计算机上的一个目录,它缓存远程下载的构件并包含尚未发布的临时构件。
settings.xml
文件中可以看到 Maven 的本地仓库路径配置,默认本地仓库路径是在${user.home}/.m2/repository
。 - 远程仓库:官方或者其他组织维护的 Maven 仓库。
Maven 远程仓库可以分为:
- 中央仓库:这个仓库是由 Maven 社区来维护的,里面存放了绝大多数开源软件的包,并且是作为 Maven 的默认配置,不需要开发者额外配置。另外为了方便查询,还提供了一个查询地址open in new window,开发者可以通过这个地址更快的搜索需要构件的坐标。
- 私服:私服是一种特殊的远程 Maven 仓库,它是架设在局域网内的仓库服务,私服一般被配置为互联网远程仓库的镜像,供局域网内的 Maven 用户使用。
- 其他的公共仓库:有一些公共仓库是为了加速访问(比如阿里云 Maven 镜像仓库)或者部分构件不存在于中央仓库中。
Maven 依赖包寻找顺序:
- 先去本地仓库找寻,有的话,直接使用。
- 本地仓库没有找到的话,会去远程仓库找寻,下载包到本地仓库。
- 远程仓库没有找到的话,会报错。