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.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缓存造成的,清除缓存后重试即可

展开阅读全文

没有更多推荐了,返回首页