赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
我们在java应用程序的编译或者运行时可能会遇到抛出java.lang.ClassNotFoundException、java.lang.NoSuchMethodError、java.lang.NoClassDefFoundError这类的异常。这类异常一般都是jar包冲突导致的。那么jar包冲突的原因是什么?如何解决jar包冲突呢?我们一起来看下。
Jar包冲突问题的本质:Java应用程序因某种因素,加载不到正确的类而致使其行为跟预期不一致。
具体来讲可分为两种状况:
1)第一类Jar包冲突问题:应用程序依赖的同一个Jar包出现了多个不一样版本,并选择了错误的版本而致使JVM加载不到需要的类或加载了错误版本的类;
2)第二类Jar包冲突问题:一样的类(类的全限定名彻底同样)出现在多个不一样的依赖Jar包中,即该类有多个版本,并因为Jar包加载的前后顺序致使JVM加载了错误版本的类。
这两种状况所致使的结果实际上是同样的,都会使应用程序加载不到正确的类,那么其行为就会跟预期不一致了,下面对这两种类型进行详细分析。
随着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包的冲突问题有如下三个必要条件:
传递性依赖是Maven2.0引入的新特性,让开发人员只需关注直接依赖的Jar包,对于间接依赖的Jar包,Maven会经过解析从远程仓库获取的依赖包的pom文件来隐式地将其引入,为开发带来了极大的便利。但同时也带来了版本冲突的问题,即同一个Jar包出现了多个不一样的版本。针对该问题Maven也有一套仲裁机制来决定最终选用哪一个版本,但Maven的选择不一定是开发人员所指望的,这也是产生Jar包冲突最多见的原因。先来看下Maven的仲裁机制:
从maven的仲裁机制中能够发现,除了第一条仲裁规则(这也是解决Jar包冲突的经常使用手段之一)外,后面的两条原则,对于同一个Jar包不一样版本的选择,也许是maven研发团队在总结了大量的项目依赖管理经验后得出的两条结论,又或者是发现根本找不到一种统一的方式来知足全部场景以后的无奈之举。可能这对于多数场景是适用的,可是它不一定适合我们当前的应用。由于每一个应用都有其特殊性,该依赖哪一个版本,maven没办法帮你彻底搞定,如果你没有规规矩矩地使用**<dependencyManagement>**来进行依赖管理,就注定了逃脱不了第一类Jar包冲突问题。
对于第二类Jar包冲突问题,即多个不一样的Jar包有类冲突,这相对于第一类问题就显得更为棘手。为什么这么说呢?在这种状况下,两个不一样的Jar包,假设为 A、 B,它们的名称互不相同,甚至可能彻底不沾边,若是不是出现冲突问题,你可能都不会发现它们有共有的类!对于A、B这两个Jar包,maven就显得无能为力了,由于maven只会为你针对同一个Jar包的不一样版本进行仲裁,而这俩是属于不一样的Jar包,超出了maven的依赖管理范畴。此时,当A、B都出现在应用程序的类路径下时,就会存在潜在的冲突风险,即A、B的加载前后顺序就决定着JVM最终选择的类版本,若是选错了,就会出现诡异的第二类冲突问题。
那么Jar包的加载顺序都由哪些因素决定的呢?具体以下:
Jar包冲突可能会导致哪些问题?一般发生在编译或运行时,主要分为两类问题:一类是比较直观的也是最为常见的错误是抛出各类运行时异常,还有一类就是比较隐晦的问题,它不会报错,其表现形式是应用程序的行为跟预期不一致,具体问题如下:
1) 如果有异常堆栈信息,根据错误信息便可定位致使冲突的类名,而后在eclipse中CTRL+SHIFT+T
或者在idea中CTRL+N
就可发现该类存在于多个依赖Jar包中
2)如果步骤1没法定位冲突的类来自哪一个Jar包,可在应用程序启动时加上JVM参数-verbose:class
或者-XX:+TraceClassLoading
,日志里会打印出每一个类的加载信息,如来自哪一个Jar包
3)定位了冲突类的Jar包以后,经过mvn dependency:tree -Dverbose -Dincludes=<groupId>:<artifactId>
查看是哪些地方引入的Jar包的这个版本
4)肯定Jar包来源以后,如果是第一类Jar包冲突,则可用**<excludes>排除不须要的Jar包版本或者在依赖管理<dependencyManagement>中申明版本;如果第二类Jar包冲突,可以排除的话,则用<excludes>**排掉不须要的那个Jar包,如果不能排除,则需考虑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包冲突问题:第一类是同一个Jar包出现了多个不一样版本,第二类是同一个类出现在多个不同的Jar包中。
然后介绍了第一类Jar包冲突问题的原因是是因为Maven仲裁机制并不能只能的判断应用真正需要的Jar包版本,第二类Jar包冲突问题的原因是受到Jar包的类加载器的加载顺序和操作系统的文件加载顺序的影响。
最后提出了问题排除和解决的方案,以及如何有效避免Jar包冲突问题的发生。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。