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排除

    <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.ClassNotFoundExceptionMethod不兼容等异常,这时你可以设置一个断点,在断点处通过下面这个工具类来查看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缓存造成的,清除缓存后重试即可

  • 27
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值