Maven JAR包问题排查及解决方案
前言
写这篇文章的初衷是因为今天在使用mvn dependency:tree
命令时,突然想起一年前面试阿里的一道面试题。面试题是说假设线上发生JAR
包冲突,应该怎么排查?我那时候的回答是IDEA
有个Maven Helper
的插件,可以帮忙分析依赖冲突,然后还有一种办法是如果一个类import
的时候提示两个地方可导入,那就说明有冲突。现在回头想想确实太不专业了,以下是一次JAR
包冲突的一个比较正规的流程,是通过整理几篇博客后总结的希望对大家也有帮助,如果有错误的地方也欢迎指出
-
JAR
冲突产生的原因Pom.xml / \ B C / \ / \ X Y X M
在以上依赖关系中项目除了会引入B、C还会引入X、Y、M的依赖包,但是如果B依赖的X版本会1.0而C依赖的X版本为2.0时,那最后项目使用的到底是X的1.0版本还是2.0版本就无法确定了。这是就要看
ClassLoader
的加载顺序,假设ClassLoader
先加载1.0版本那就不会加载2.0版本,反之同理 -
使用
mvn -Dverbose dependency:tree
排查冲突[INFO] +- org.apache.tomcat:tomcat-servlet-api:jar:7.0.70:compile [INFO] +- org.apache.tomcat:tomcat-jsp-api:jar:7.0.70:compile [INFO] | +- org.apache.tomcat:tomcat-el-api:jar:7.0.70:compile [INFO] | \- (org.apache.tomcat:tomcat-servlet-api:jar:7.0.70:compile - omitted for duplicate) [INFO] +- net.sf.jasperreports:jasperreports:jar:5.6.0:compile [INFO] | +- (commons-beanutils:commons-beanutils:jar:1.8.0:compile - omitted for conflict with 1.8.3) [INFO] | +- commons-collections:commons-collections:jar:3.2.1:compile [INFO] | +- commons-digester:commons-digester:jar:2.1:compile [INFO] | | +- (commons-beanutils:commons-beanutils:jar:1.8.3:compile - omitted for duplicate) [INFO] | | \- (commons-logging:commons-logging:jar:1.1.1:compile - omitted for duplicate)
递归依赖的关系列的算是比较清楚了,每行都是一个jar包,根据缩进可以看到依赖的关系
- 最后写着
compile
的就是编译成功的 - 最后写着
omitted for duplicate
的就是有JAR
包被重复依赖了,但是JAR
包的版本是一样的 - 最后写着
omitted for conflict with xx
的,说明和别的JAR
包版本冲突了,该行的JAR
包不会被引入
该命令可配合-Dincludes和-Dexcludes进行使用,只输出自己感兴趣/不感兴趣的JAR 参数格式为:[groupId]:[artifactId]:[type]:[version] 每个部分(冒号分割的部分)是支持*通配符的,如果要指定多个格式则可以用,分割,如: mvn dependency:tree -Dincludes=javax.servlet,org.apache.*
- 最后写着
-
解决冲突,使用
exclusion
标签将冲突的JAR
排除<dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.8.3.2</version> <exclusions> <exclusion> <artifactId>guava</artifactId> <groupId>com.google.guava</groupId> </exclusion> <exclusion> <artifactId>spring</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency>
解决了冲突后的树(解决冲突的策略是:就近原则,即离根近的依赖被采纳)
-
查看运行期类来源的JAR包
有时你以为解决了但是偏偏还是报类包冲突,典型症状是
java.lang.ClassNotFoundException
或Method
不兼容等异常,这时你可以设置一个断点,在断点处通过下面这个工具类来查看Class
所来源的JAR
包public class ClassLocationUtils { public static String where(final Class clazz) { if (clazz == null) { throw new IllegalArgumentException("null input: cls"); } URL result = null; final String clazzAsResource = clazz.getName().replace('.', '/').concat(".class"); final ProtectionDomain protectionDomain = clazz.getProtectionDomain(); if (protectionDomain != null) { final CodeSource codeSource = protectionDomain.getCodeSource(); if (codeSource != null) result = codeSource.getLocation(); if (result != null) { if ("file".equals(result.getProtocol())) { try { if (result.toExternalForm().endsWith(".jar") || result.toExternalForm().endsWith(".zip")) { result = new URL("jar:".concat(result.toExternalForm()).concat("!/").concat(clazzAsResource)); } else if (new File(result.getFile()).isDirectory()) { result = new URL(result, clazzAsResource); } } catch (MalformedURLException ignore) { } } } } if (result == null) { final ClassLoader clsLoader = clazz.getClassLoader(); result = clsLoader != null ? clsLoader.getResource(clazzAsResource) : ClassLoader.getSystemResource(clazzAsResource); } return result.toString(); } }
然后随便写一个测试设置好断点,在执行到断点出使用
ALT+F8
动态执行代码,如ClassLocationUtils.where(Logger.class)
即可马上找到对应的
JAR
,如果这个JAR
不是你期望的就说明是IDE
缓存造成的,清除缓存后重试即可
上一篇: 解决“UserWarning: Matplotlib is currently using agg, which is a non-GUI backend....”
下一篇: 导入maven项目很慢的问题解决方案
推荐阅读
-
IDEA引MAVEN项目jar包依赖导入问题解决方法
-
Maven将依赖包、jar/war包及配置文件输出到指定目录
-
IDEA中创建maven项目引入相关依赖无法下载jar问题及解决方案
-
解决IDEA中Maven依赖包导入失败报红问题(总结最有效8种解决方案)
-
解决maven找不到jar包的问题
-
解决maven无法下载依赖的jar包的问题
-
IDEA 程序包不存在,找不到符号但是明明存在对应的jar包(问题分析及解决方案)
-
解决eclipse中maven引用不到已经存在maven中jar包的问题
-
Maven 本地仓库明明有jar包,但是pom文件还是报错的问题记录
-
关于maven项目中*仓库无法下载jar包,需要手动导入jar包的的问题