问题由来
作为java开发的我们,一定会经常遇到下面这两个看似非常类似的异常信息:
java.lang.NoSuchMethodError
java.lang.ClassNotFoundException
maven 预备知识
maven 依赖解析规则:
Rule 1: Nearest First;
Rule 2: If distance is the same, the one who declare first wins;
parent pom
玩过spring boot的人,一定都知道,SB提供一个spring-boot-starter-parent
。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
然后就可以在<dependencies>
里面引入自己想要使用的starter,并且在使用时无需指定starter的具体版本,默认使用什么parent引入的版本号。当然,maven支持定义version标签来更改特定的starter的版本号。
在Parent POM中,一般用于定义子项目必须遵守的一些约定,如Java版本,编码,公共第三方依赖的版本,测试规范,maven版本检查,打包规范,发布仓库声明等。
通过该规范,可以保证子项目在打包、发布、版本依赖、环境配置等方面的一致性,减少冲突和不规范,降低开发环境配置复杂度。
Parent POM中只定义dependencyManagement,不会帮项目引入任何依赖。所以用户如果需要使用某个依赖,还需要自己显式的引入(不要加version)
BOM
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
BOM(Bill of Materials)定义一整套相互兼容的jar包版本集合,使用时只需要依赖该BOM文件,即可放心的使用需要的依赖jar包,且无需再指定版本号。BOM的维护方负责版本升级,并保证BOM中定义的jar包版本之间的兼容性。
为什么需要BOM?
使用BOM除了可以方便使用者在声明依赖的客户端时不需要指定版本号外,最主要的原因是可以解决依赖冲突。
考虑以下的依赖场景:
Project A依赖B 2.1.3和C 1.2.0版本;
B 2.1.3依赖D 1.1.6版本;
C 1.2.0依赖D 1.3.0版本。
则 Project A对于D的依赖就出现冲突,按照maven dependency mediation的规则,最后生效的是1.1.6版本(就近原则)。
在这种情况下,由于C依赖1.3.0版本的D,但是在运行时生效的确是1.1.6版本,所以在运行时很容易产生问题,如 NoSuchMethodError, ClassNotFoundException等。
如何定义BOM?
BOM本质上是一个普通的POM文件,区别是对于使用方而言,生效的只有<dependencyManagement>
这一个部分。只需要在<dependencyManagement>
定义对外发布的客户端版本即可。
dependency management takes precedence over dependency mediation for transitive dependencies.
如何使用BOM?
首先需要在pom.xml文件的<dependencyManagement>
中,声明对BOM的依赖,然后在实际使用依赖的地方把版本去掉即可。