接手了一套比拟有年代感的零碎,打算把重构及遇到的问题写成系列文章,老树发新枝,重温一些实战技术,分享给大家。【重构 02 篇】:Maven 我的项目 Jar 包管理机制、抵触解决。
常识背景
Jar 包抵触在软件开发过程中是不可避免的,因而,如何疾速定位抵触源,了解抵触导致的过程及底层原理,是每个程序员的必修课。也是晋升工作效率、应答面试、在团队中怀才不遇的机会。
实际中可能直观感触到的 Jar 包抵触体现往往有这几种:
- 程序抛出
java.lang.ClassNotFoundException
异样; - 程序抛出
java.lang.NoSuchMethodError
异样; - 程序抛出
java.lang.NoClassDefFoundError
异样; - 程序抛出
java.lang.LinkageError
异样等;
这是可能直观出现的,当然还有隐性的异样,比方程序执行后果与预期不符等。上面,咱们就剖析一下 Maven 我的项目中 Jar 包的解决机制及引起抵触的起因。
Maven Jar 包管理机制
在 Maven 我的项目中,想要理解 Jar 抵触必须得先理解一下 Maven 是如何治理的 Jar 包的。这波及到 Maven 的一些个性,比方依赖传递、最短门路优先准则、最先申明准则等。
依赖传递准则
当在 Maven 我的项目中引入 A 的依赖,A 的依赖通常又会引入 B 的 jar 包,B 可能还会引入 C 的 jar 包。这样,当你在 pom.xml 文件中增加了 A 的依赖,Maven 会主动的帮你把所有相干的依赖都增加进来。
比方,在 Spring Boot 项中,当引入了 spring-boot-starter-web:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
此时 Maven 的依赖构造可能是这样的:
下面这种关系,咱们就能够了解为依赖的传递性。即当一个依赖须要另外一个依赖撑持时,Maven 会帮咱们把相应的依赖顺次增加到我的项目当中。
这样的益处是,应用起来就十分不便,不必本人挨个去找依赖 Jar 包了。害处是会引起 Jar 包抵触,咱们前面会讲到。
最短门路优先准则
依赖链路一:次要依据依赖的门路长短来决定引入哪个依赖(两个抵触的依赖)。
举例说明:
依赖链路一:A -> X -> Y -> Z(21.0)
依赖链路二:B -> Q -> Z(20.0)
我的项目中同时引入了 A 和 B 两个依赖,它们间接都引入了 Z 依赖,但因为 B 的依赖链路比拟短,因而最终失效的是 Z(20.0)版本。这就是最短门路优先准则。
此时如果 Z 的 21.0 版本和 20.0 版本区别较大,那么就会产生 Jar 包抵触的体现。
最先申明优先准则
如果两个依赖的门路一样,最短门路优先准则是无奈进行判断的,此时须要应用 最先申明优先准则,也就是说,谁的申明在前则优先选择。
举例说明:
依赖链路一:A -> X -> Z(21.0)
依赖链路二:B -> Q -> Z(20.0)
A 和 B 最终都依赖 Z,此时 A 的申明(pom 中引入的程序)优先于 B,则针对抵触的 Z 会优先引入 Z(21.0)。
如果 Z(21.0)向下兼容 Z(20.0),则不会呈现 Jar 包抵触问题。但如果将 B 申明放后面,则有可能会产生 Jar 包抵触。
Jar 包抵触产生的起因
下面讲了 Maven 保护 Jar 包的三个准则,其实每个准则会产生什么样的 Jar 包抵触,曾经大略理解了。这里再来一个综合示例。
举例说明:
依赖链路一:A -> B -> C -> G21(guava 21.0)
依赖链路二:D -> F -> G20(guava 20.0)
假如我的项目中同时引入了 A 和 D 的依赖,依照依赖传递机制和默认依赖调节机制(第一:门路最近者优先;第二:第一申明优先),默认会引入 G20 版本的 Jar 包,而 G21 的 Jar 包不会被援用。
如果 C 中的办法应用了 G21 版本中的某个新办法(或类),因为 Maven 的解决,导致 G21 并未被引入。此时,程序在调用对应 类时便会抛出 ClassNotFoundException
异样,调用对应 办法 时便会抛出 NoSuchMethodError
异样。
排查定位 Jar 包抵触
在高版本的 IDEA 中曾经自带了 Maven 依赖治理插件,顺次执行:关上 pom.xml 文件,在文件内右击,抉择 Maven,抉择 Show Dependencies 即可查看 Maven 的依赖层级构造:
执行之后展现的成果便是最开始的 spring-boot-web 那样成果,在图中能够分明的看到都应用了哪些依赖,它们的层级,是否有抵触的 jar 包等。抵触局部会用红色标出,同时标出 Maven 默认抉择了哪个版本。
如果你的 IDEA 版本中默认没有 Maven 治理插件,也可装置 Maven Helper,通过这块插件来帮你剖析 Jar 包抵触。
装置完插件,重启之后,关上 pom.xml 文件,在文件上面的 Dependency Analyzer 视图中便能够看到 Jar 包抵触的后果剖析:
此时,对于哪些 Jar 包抵触了,便高深莫测。同时,能够右击抵触的 Jar 包,执行”Exclude“进行排除,在 pom.xml 中便会主动增加排除 jar 包的属性。
对于本地环境能够利用 Maven Helper 等插件来解决,但在预生产或生成环境中就没那么不便了。此时能够通过 mvn
命令来定位突出的细节。
执行如下 mvn
命令:
mvn dependency:tree -Dverbose
留神不要省略-Dverbose
,要不然不会显示被疏忽的包。
执行后果如下:
通过这种模式,也能够清晰的看出哪些 Jar 包产生了抵触。
如何对立 Jar 包依赖
像下面截图所示,如果一个我的项目有 N 多个子项目形成,我的项目之间可能还有依赖关系,Jar 包抵触不可避免,此时可采纳父 pom,对立对版本进行治理,一劳永逸。
通常做法,是在 parent 模块的 pom 文件中尽可能地申明所有相干依赖 Jar 包的版本,并在子 pom 中简略援用(不再指定版本)该构件即可。
比方在父 pom.xml 中定义 Lombok 的版本:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
</dependencyManagement>
在子 module 中便可定义如下:
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
通过这种形式,所有的子 module 中都采纳了对立的版本。
解决 Jar 包抵触的办法
这里基于 Maven 我的项目介绍几种场景下解决 Jar 抵触的办法:
- Maven 默认解决:采纳此种办法,要牢记 Maven 依赖调节机制的根本准则,门路最近者优先和第一申明优先;
- 排除法:下面 Maven Helper 的实例中曾经讲到,能够将抵触的 Jar 包在 pom.xml 中通过
exclude
来进行排除; - 版本锁定法:如果我的项目中依赖同一 Jar 包的很多版本,一个个排除十分麻烦,此时可用 版本锁定法 ,即间接明确引入指定版本的依赖。依据后面介绍 Maven 解决 Jar 包根本准则,此种形式的优先级最高。这种办法个别采纳下面咱们讲到的 如何对立 Jar 包依赖 的形式。
Jar 包抵触的实质
下面讲了 Maven 对我的项目中 Jar 包抵触的解决准则和实战层面的解决方案,但并未波及到 Jar 包抵触的实质。对于 Jar 包抵触的实质在《从 Jar 包抵触搞到类加载机制,就是这么霸气》一文中曾经进行具体的解说了。这里再针对其中的几个关键点进行概述一下。
Jar 包抵触的实质:Java 应用程序因某种因素,加载不到正确的类而导致其行为跟预期不统一。
具体分两种状况:
- 状况一:我的项目依赖了同一 Jar 包的多个版本,并且选错了版本;
- 状况二:同样的类在不同的 Jar 包中呈现,导致 JVM 加载了谬误的类;
状况一,也是本文重点探讨的场景,也就是说引入了多个 Jar 包版本,不同的 Jar 包版本有不同的类和办法。因为(不懂)Maven 依赖树的仲裁机制导致 Maven 加载了谬误的 Jar 包,从而导致 Jar 包抵触;
状况二,同一类在不同的 Jar 包中呈现(上篇文章中有详细描述)。这种状况是因为 JVM 的同一个类加载器对于同一个类只会加载一次,当初加载一个类之后,同全限定名的类便不会进行加载,从而呈现 Jar 包抵触的问题。
针对第二种状况,如果不是类抵触抛出了异样,你可能基本意识不到,所以就显得更为辣手。这种状况就能够采纳前文所述的通过剖析不同类加载器的优先级及加载门路、文件系统的文件加载程序等进行调整来解决。
小结
除了上述的办法,还很多小技巧来排查类抵触,比方通过 IDE 提供的搜寻性能,间接搜寻抛异样的类,看看是否存在多个,是否应用的是预期的版本等。这些技巧须要在实际的过程中一直的摸索和积攒。
总之,无论我的项目如许宏大,依赖如许简单,只有牢记导致抵触的起因,及解决抵触的几个形式,仔细剖析,总会有迹可循的。看完这篇文章,实际一下,你可能就会在团队中怀才不遇,成为Jar 包抵触终结者。
博主简介:《SpringBoot 技术底细》技术图书作者,热爱钻研技术,写技术干货文章。
公众号:「程序新视界」,博主的公众号,欢送关注~
技术交换:请分割博主微信号:zhuan2quan