Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
jar包冲突怎么解决_java常用jar包下载,希望能够帮助你!!!。
Jar包冲突是老生常谈的问题,几乎每一个Java程序猿都不可避免地遇到过,并且也都能想到通常的原因一般是同一个Jar包由于maven传递依赖等原因被引进了多个不同的版本而导致,可采用依赖排除、依赖管理等常规方式来尝试解决该问题,但这些方式真正能彻底解决该冲突问题吗?答案是否定的。笔者之所以将文章题目起为“重新看待”,是因为之前对于Jar包冲突问题的理解仅仅停留在前面所说的那些,直到在工作中遇到的一系列Jar包冲突问题后,才发现并不是那么简单,对该问题有了重新的认识,接下来本文将围绕Jar包冲突的问题本质和相关的解决方案这两个点进行阐述。
Jar包冲突问题
Jar包冲突的本质是什么?Google了半天也没找到一个让人满意的完整定义。其实,我们可以从Jar包冲突产生的结果来总结,在这里给出如下定义(此处如有不妥,欢迎拍砖-):
Java应用程序因某种因素,加载不到正确的类而导致其行为跟预期不一致。
具体来说可分为两种情况:1)应用程序依赖的同一个Jar包出现了多个不同版本,并选择了错误的版本而导致JVM加载不到需要的类或加载了错误版本的类,为了叙述的方便,笔者称之为第一类Jar包冲突问题;2)同样的类(类的全限定名完全一样)出现在多个不同的依赖Jar包中,即该类有多个版本,并由于Jar包加载的先后顺序导致JVM加载了错误版本的类,称之为第二类Jar包问题。这两种情况所导致的结果其实是一样的,都会使应用程序加载不到正确的类,那其行为自然会跟预期不一致了,以下对这两种类型进行详细分析。
随着Jar包迭代升级,我们所依赖的开源的或公司内部的Jar包工具都会存在若干不同的版本,而版本升级自然就避免不了类的方法签名变更,甚至于类名的更替,而我们当前的应用程序往往依赖特定版本的某个类 M ,由于maven的传递依赖而导致同一个Jar包出现了多个版本,当maven的仲裁机制选择了错误的版本时,而恰好类 M在该版本中被去掉了,或者方法签名改了,导致应用程序因找不到所需的类 M或找不到类 M中的特定方法,就会出现第一类Jar冲突问题。可总结出该类冲突问题发生的以下三个必要条件:
同样的类出现在了应用程序所依赖的两个及以上的不同Jar包中,这会导致什么问题呢?我们知道,同一个类加载器对于同一个类只会加载一次(多个不同类加载器就另说了,这也是解决Jar包冲突的一个思路,后面会谈到),那么当一个类出现在了多个Jar包中,假设有 A 、 B 、 C 等,由于Jar包依赖的路径长短、声明的先后顺序或文件系统的文件加载顺序等原因,类加载器首先从Jar包 A 中加载了该类后,就不会加载其余Jar包中的这个类了,那么问题来了:如果应用程序此时需要的是Jar包 B 中的类版本,并且该类在Jar包 A 和 B 中有差异(方法不同、成员不同等等),而JVM却加载了Jar包 A 的中的类版本,与期望不一致,自然就会出现各种诡异的问题。
从上面的描述中,可以发现出现不同Jar包的冲突问题有以下三个必要条件:
当前maven大行其道,说到第一类Jar包冲突问题的产生原因,就不得不提maven的依赖机制了。传递性依赖是Maven2.0引入的新特性,让我们只需关注直接依赖的Jar包,对于间接依赖的Jar包,Maven会通过解析从远程仓库获取的依赖包的pom文件来隐式地将其引入,这为我们开发带来了极大的便利,但与此同时,也带来了常见的问题——版本冲突,即同一个Jar包出现了多个不同的版本,针对该问题Maven也有一套仲裁机制来决定最终选用哪个版本,但Maven的选择往往不一定是我们所期望的,这也是产生Jar包冲突最常见的原因之一。先来看下Maven的仲裁机制:
从maven的仲裁机制中可以发现,除了第一条仲裁规则(这也是解决Jar包冲突的常用手段之一)外,后面的两条原则,对于同一个Jar包不同版本的选择,maven的选择有点“一厢情愿”了,也许这是maven研发团队在总结了大量的项目依赖管理经验后得出的两条结论,又或者是发现根本找不到一种统一的方式来满足所有场景之后的无奈之举,可能这对于多数场景是适用的,但是它不一定适合我——当前的应用,因为每个应用都有其特殊性,该依赖哪个版本,maven没办法帮你完全搞定,如果你没有规规矩矩地使用<dependencyManagement>来进行依赖管理,就注定了逃脱不了第一类Jar包冲突问题。
对于第二类Jar包冲突问题,即多个不同的Jar包有类冲突,这相对于第一类问题就显得更为棘手。为什么这么说呢?在这种情况下,两个不同的Jar包,假设为 A、 B,它们的名称互不相同,甚至可能完全不沾边,如果不是出现冲突问题,你可能都不会发现它们有共有的类!对于A、B这两个Jar包,maven就显得无能为力了,因为maven只会为你针对同一个Jar包的不同版本进行仲裁,而这俩是属于不同的Jar包,超出了maven的依赖管理范畴。此时,当A、B都出现在应用程序的类路径下时,就会存在潜在的冲突风险,即A、B的加载先后顺序就决定着JVM最终选择的类版本,如果选错了,就会出现诡异的第二类冲突问题。
那么Jar包的加载顺序都由哪些因素决定的呢?具体如下:
Jar包冲突可能会导致哪些问题?通常发生在编译或运行时,主要分为两类问题:一类是比较直观的也是最为常见的错误是抛出各种运行时异常,还有一类就是比较隐晦的问题,它不会报错,其表现形式是应用程序的行为跟预期不一致,分条罗列如下:
解决方案
从上一节的解决方案可以发现,当出现第二类Jar包冲突,且冲突的Jar包又无法排除时,问题变得相当棘手,这时候要处理该冲突问题就需要较大成本了,所以,最好的方式是在冲突发生之前能有效地规避之!就好比数据库死锁问题,死锁避免和死锁预防就显得相当重要,若是等到真正发生死锁了,常规的做法也只能是回滚并重启部分事务,这就捉襟见肘了。那么怎样才能有效地规避Jar包冲突呢?
对于第一类Jar包冲突问题,通常的做法是用<excludes>排除不需要的版本,但这种做法带来的问题是每次引入带有传递性依赖的Jar包时,都需要一一进行排除,非常麻烦。maven为此提供了集中管理依赖信息的机制,即依赖管理元素<dependencyManagement>,对依赖Jar包进行统一版本管理,一劳永逸。通常的做法是,在parent模块的pom文件中尽可能地声明所有相关依赖Jar包的版本,并在子pom中简单引用该构件即可。
来看个示例,当开发时确定使用的httpclient版本为4.5.1时,可在父pom中配置如下:
... <properties> <httpclient.version>4.5.1</httpclient.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>${httpclient.version}</version> </dependency> </dependencies> </dependencyManagement> ...
然后各个需要依赖该Jar包的子pom中配置如下依赖:
... <dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> </dependencies> ...
对于第二类Jar包冲突问题,前面也提到过,其核心在于同名类出现在了多个不同的Jar包中,如果人工来排查该问题,则需要逐个点开每个Jar包,然后相互对比看有没同名的类,那得多么浪费精力啊?!好在这种费时费力的体力活能交给程序去干。maven-enforcer-plugin,这个强大的maven插件,配合extra-enforcer-rules工具,能自动扫描Jar包将冲突检测并打印出来,汗颜的是,笔者工作之前居然都没听过有这样一个插件的存在,也许是没遇到像工作中这样的冲突问题,算是涨姿势了。其原理其实也比较简单,通过扫描Jar包中的class,记录每个class对应的Jar包列表,如果有多个即是冲突了,故不必深究,我们只需要关注如何用它即可。
在最终需要打包运行的应用模块pom中,引入maven-enforcer-plugin的依赖,在build阶段即可发现问题,并解决它。比如对于具有parent pom的多模块项目,需要将插件依赖声明在应用模块的pom中。这里有童鞋可能会疑问,为什么不把插件依赖声明在parent pom中呢?那样依赖它的应用子模块岂不是都能复用了?这里之所以强调“打包运行的应用模块pom”,是因为冲突检测针对的是最终集成的应用,关注的是应用运行时是否会出现冲突问题,而每个不同的应用模块,各自依赖的Jar包集合是不同的,由此而产生的<ignoreClasses>列表也是有差异的,因此只能针对应用模块pom分别引入该插件。
先看示例用法如下:
... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>1.4.1</version> <executions> <execution> <id>enforce</id> <configuration> <rules> <dependencyConvergence/> </rules> </configuration> <goals> <goal>enforce</goal> </goals> </execution> <execution> <id>enforce-ban-duplicate-classes</id> <goals> <goal>enforce</goal> </goals> <configuration> <rules> <banDuplicateClasses> <ignoreClasses> <ignoreClass>javax.*</ignoreClass> <ignoreClass>org.junit.*</ignoreClass> <ignoreClass>net.sf.cglib.*</ignoreClass> <ignoreClass>org.apache.commons.logging.*</ignoreClass> <ignoreClass>org.springframework.remoting.rmi.RmiInvocationHandler</ignoreClass> </ignoreClasses> <findAllDuplicates>true</findAllDuplicates> </banDuplicateClasses> </rules> <fail>true</fail> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>org.codehaus.mojo</groupId> <artifactId>extra-enforcer-rules</artifactId> <version>1.0-beta-6</version> </dependency> </dependencies> </plugin>
maven-enforcer-plugin是通过很多预定义的标准规则(standard rules)和用户自定义规则,来约束maven的环境因素,如maven版本、JDK版本等等,它有很多好用的特性,具体可参见官网。而Extra Enforcer Rules则是MojoHaus项目下的针对maven-enforcer-plugin而开发的提供额外规则的插件,这其中就包含前面所提的重复类检测功能,具体用法可参见官网,这里就不详细叙述了。
典型案例
这类Jar包冲突是最常见的也是相对比较好解决的,已经在三、冲突的表象这节中列举了部分案例,这里就不重复列举了。
Spring2.5.6与Spring3.x,从单模块拆分为多模块,Jar包名称(artifactId)也从spring变为spring-submoduleName,如
spring-context、spring-aop等等,并且也有少部分接口改动(Jar包升级的过程中,这也是在所难免的)。由于是不同的Jar包,经maven的传递依赖机制,就会经常性的存在这俩版本的Spring都在classpath中,从而引发潜在的冲突问题。
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章