java 单机接口限流处理方案
369
2022-11-01
详解Maven JAR包冲突问题排查及解决方案
前言
写这篇文章的初衷是因为今天在使用mvn dependency:tree命令时,突然想起一年前面试阿里的一道面试题。面试题是说假设线上发生JAR包冲突,应该怎么排查?我那时候的回答是IDEA有个Maven Helper的插件,可以帮忙分析依赖冲突,然后还有一种办法是如果一个类import的时候提示两个地方可导入,那就说明有冲突。现在回头想想确实太不专业了,以下是一次JAR包冲突的一个比较正规的流程,是通过整理几篇博客后总结的希望对大家也有帮助,如果有错误的地方也欢迎指出
github地址:https://github.com/RobertoHuang
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排除
解决了冲突后的树(解决冲突的策略是:就近原则,即离根近的依赖被采纳)
查看运行期类来源的JAR包
有时你以为解决了但是偏偏还是报类包冲突,典型症状是java.lang.ClassNotFoundException或Method不兼容等异常,这时你可以设置一个断点,http://在断点处通过下面这个工具类来查看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缓存造成的,清除缓存后重试即可
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~