Maven 是一个流行的构建工具,用于管理和构建Java项目。它遵循一些核心原则,以确保项目的构建和依赖管理能够高效、一致地进行。以下是您提到的三大原则的解释:
在Maven中,最短路径优先原则指的是当解决项目依赖关系时,Maven会尽量选择最短路径来满足这些依赖关系。这意味着当有多个不同版本的依赖项可供选择时,Maven会选择路径最短的那个版本。这有助于避免由于依赖版本冲突而引发的问题,因为最短路径通常是最直接和合理的依赖选择。
举例:
当项目直接依赖一个 C-api-1.0 和 A-api-2.1 包,并且 C-api-1.0 有如下间接依赖关系: C-api-1.0 —> B-api-1.0 —> A-api-1.1
这时候项目里包含了 A-api 的 1.1 和 2.1 两个版本,由于存在最短路径原则明显 Project —> A-api-2.1
短于 Project —> C-api-1.0 —> B-api-1.0 —> A-api-1.1
故 Project 项目里会使用 A-api-2.1
示例项目中包含如下依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>3.10-FINALversion>
dependency>
<dependency>
<artifactId>QLExpressartifactId>
<groupId>com.alibabagroupId>
<version>3.2.2version>
dependency>
dependencies>
其中 poi-ooxml 存在如下依赖关系: poi-ooxml-->poi-->commons-logging (版本 1.1 )
QLExpress 存在如下依赖关系: QLExpress —> commons-logging (版本 1.1.1 )
由于存在最短路径原则,明显 QLExpress —> commons-logging
路径更短,项目会使用 commons-logging 的 1.1.1 版本
POM(Project Object Model)是Maven项目的核心配置文件,其中定义了项目的依赖、插件、构建配置等信息。POM 文件中声明顺序优先原则强调了在POM 文件中元素的声明顺序对于项目的继承和覆盖机制的影响。具体来说,子项目的POM 文件会继承父项目的POM 配置,但是如果子项目在自己的POM 文件中声明了相同的配置元素,那么子项目的声明会覆盖父项目的相应声明。因此,为了更好地理解和管理项目的继承和覆盖关系,声明的顺序在一些情况下变得很重要。
当项目里存在直接依赖 C-api-1.0 和 B-api-1.0 其中存在如下间接依赖关系: C-api-1.0 —> A-api-2.1 B-api-1.0 —> A-api-1.1
这时项目间接依赖了 A-api 的 2.1 和 1.1 两个版本,由于存在 POM 文件中申明顺序优先原则,故项目中会使用 A-api-2.1
示例项目中存在如下依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>3.10-FINALversion>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-scratchpadartifactId>
<version>3.17-beta1version>
dependency>
dependencies>
其中 poi-ooxml 依赖了包 poi ,poi-scratchpad 也依赖了 poi 包,但是 poi-ooxml 依赖了 poi 包的 3.10-FINAL 版本, poi-scratchpad 依赖了 poi 包的 3.17-beta1 版本,由于存在申明顺序优先原则,项目会使用 poi 包的 3.10-FINAL 版本
覆盖优先原则是指当一个项目依赖于多个版本不同的相同依赖项时,Maven 会选择最近声明的那个版本作为实际使用的版本。这种行为在大多数情况下能够确保项目使用最新的依赖版本,但也可能会导致意外的依赖冲突。为了避免潜在的问题,开发者需要谨慎地管理项目的依赖关系,确保所选择的依赖版本是经过充分测试和兼容性验证的。
项目父 POM 中直接依赖包 A-api-1.1 ,子模块 Module A 的 parent 直接依赖了项目的 POM ,但是同时也直接依赖了 A-api-1.2 。 由于存在覆盖优先原则子模块 Module A 中会优先使用 A-api-1.2 而不是父POM的 A-api-1.1
在以上项目工程下,新建一个子工程,在子工程POM添加如下依赖
<dependencies>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>5.2.3version>
dependency>
dependencies>
由于外部工程依赖了 poi 包的 3.10-FINAL ,但是子工程依赖了 poi 的 5.2.3 版本,故整体包依赖会包含poi的两个版本,但是在子工程中使用的是 5.2.3
这些原则有助于确保Maven项目的依赖管理和构建过程具有可预测性、一致性,并且能够最大程度地避免依赖冲突等问题。
ClassNotFoundException
当项目启动时出现 ClassNotFoundException 这样的错误,表示由于项目使用的包版本下找不到当前需要的类
1、调用 class 的 forName 方法时,找不到指定的类。
2、 ClassLoader 中的 findSystemClass() 方法时,找不到指定的类。
3、 ClassLoader 中的 loadClass() 方法时,找不到指定的类。
NoSuchMethodError
NoSuchMethodError 就是程序在运行中找不到运行的方法导致的
1、有可能发生的就是 jar 冲突,可能是两个高低版本的 jar 包导致。
2、有可能是有两个 jar 包有相同的类与方法,导致程序调用过程中找不到正确的方法。
Maven 依赖加载流程如下
首先,将 parent 的直接依赖,间接依赖,还有依赖管理,插入本项目,放入本项目的直接依赖,间接依赖还有依赖管理之前。
对于直接依赖,如果有 version,那么就依次放入 DependencyMap 中。如果没有 version ,则从依赖管理中查出来 version,之后放入 DependencyMap 中。 key 为依赖的 groupId + artifactId, value 为 version ,后放入的会把之前放入的相同 key 的 value 替换。
对于每个依赖,各自按照步骤 1 和 2 加载自己的 pom 文件,但是如果第一步中的本项目 dependency management 中有依赖的版本,使用本项目 dependency management 的依赖版本,生成 TransitiveDependencyMap ,这里面就包含了所有的间接依赖。
所有间接依赖的 TransitiveDependencyMap , 对于项目的 DependencyMap 里面没有的 key ,依次放入项目的 DependencyMap 。
如果 TransitiveDependencyMap 里面还有间接依赖,那么递归执行步骤 3 和 4 。
由于是先放入本项目的 DependencyMap ,再去递归 TransitiveDependencyMap ,这就解释了 Maven 依赖的最短路径原则
参考: MAVEN依赖的优先原则