Detailed explanation of Maven JAR packet conflict troubleshooting and solution

  • 2021-09-05 00:08:34
  • OfStack

Preface

The original intention of writing this article is that when using mvn dependency: tree command today, I suddenly remembered an interview question with Ali one year ago. The interview question means that if there is an JAR package conflict on the line, how should it be checked? My answer at that time is that IDEA has a plug-in of Maven Helper, which can help analyze dependency conflicts. Then there is another way that if a class import prompts two places to import, it means there is a conflict. Now think back really too unprofessional, the following is a conflict of JAR package a more formal process, is through sorting out a few blogs after the summary of the hope that we are also helpful, if there are mistakes are also welcome to point out

GitHub Address: https://github.com/RobertoHuang

Causes of JAR Conflict


   Pom.xml
   /  \
  B    C
 / \   / \
 X  Y  X  M

In the above dependency relationship, the project will introduce B, C will also introduce dependency packages of X, Y and M. However, if B depends on X version 1.0 and C depends on X version 2.0, it is uncertain whether the final project will use X version 1.0 or 2.0. This depends on the loading order of ClassLoader. If ClassLoader loads version 1.0 first, it will not load version 2.0, and vice versa

Troubleshooting conflicts using 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)

Recursive dependency relationship column is relatively clear, each row is a jar package, according to the indentation can see the dependency relationship

The last one with compile is compiled successfully The last one that says omitted for duplicate is that the JAR package is relied on repeatedly, but the version of the JAR package is 1 At the end of the statement, omitted for conflict with xx, the description conflicts with other JAR package versions, and the JAR package in this line will not be introduced

This command can be used in conjunction with-Dincludes and-Dexcludes, and only outputs JAR that you are interested in/not interested in
The parameter format is: [groupId]: [artifactId]: [type]: [version]
Each part (the part divided by colon) supports * wildcard character. If you want to specify multiple formats, you can use * partition, such as:


mvn dependency:tree -Dincludes=javax.servlet,org.apache.*

Resolve conflicts and use exclusion tags to eliminate conflicting 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>

The post-conflict tree is resolved (the conflict resolution strategy is: the proximity principle, that is, the dependency close to the root is adopted)

View the JAR package from the runtime class source

Sometimes you think you have solved it, but you still report a class package conflict. Typical symptoms are java. lang. ClassNotFoundException or Method incompatibility. At this time, you can set a breakpoint, and at the breakpoint, you can view the JAR package from Class through the following tool class


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();
  }
}

Then write a test randomly to set a breakpoint, and use ALT+F8 to dynamically execute the code from execution to breakpoint, such as


ClassLocationUtils.where(Logger.class)

You can immediately find the corresponding JAR. If this JAR is not what you expect, it means that it is caused by IDE cache. Clear the cache and try again


Related articles: