Earlier this year, while reading Maven in Action, I learned that Maven can rely on mediation, where packages with different versions are loaded according to certain rules to avoid conflicts. If Maven can resolve conflicts, why do you often hear “dependency conflicts occur”? If conflicts are resolved, what are the problems? It wasn’t until I ran into it at work this week that I realized what was going on. Let’s start with my actual experience.

1. Maven dependency conflict experience

Encryptor class DigestUtils, MessageDigest, HmacUtils, DigestUtils, MessageDigest, HmacUtils, DigestUtils, MessageDigest

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacUtils;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class Encryptor {

    public String encrype(String s) {
        MessageDigest sha256Digest = DigestUtils.getSha256Digest();
        String result = Hex.encodeHexString(sha256Digest.digest(s.getBytes(StandardCharsets.UTF_8)));
        return Hex.encodeHexString(HmacUtils.getHmacSha256(result.getBytes()).doFinal(result.getBytes()));
    }

    public static void main(String[] args) {
        Encryptor encryptor = new Encryptor();
        String s = "test";
        String result = encryptor.encrype(s);
        System.out.println(result);
    }
    
    /** output: fdd04dcac94e9803a72e4268141f773e2024a8fe46ba19a263be22c5ca83e931 **/

}
Copy the code

Executing unit tests works fine. However, an IllegalAccessError error is reported when the entire application is started.

Unit tests under module Y run without an error, but an IllegalAccessError occurs when the entire application starts and the X module, which is the entry point to the program, calls the Encryptor in module Y. According to the specific error message in the figure, it means that I have no permission to access the getSha256Digest method. I click Ctrl+B to access the getSha256Digest method, as follows:

The getSha256Digest method is a public access level, and I was blindsided. Because this method is so simple, I’m not going to use it, I’m going to write it this way.

public String encrype(String s) {
    try {
        MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
        String result = Hex.encodeHexString(sha256Digest.digest(s.getBytes(StandardCharsets.UTF_8)));
        return Hex.encodeHexString(HmacUtils.getHmacSha256(result.getBytes()).doFinal(result.getBytes()));
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return "error"; }}Copy the code

Another error. Well, there’s no hiding! Error as follows:

Class HmacUtils not found (ClassNotFoundException) But if I Ctrl+B, the class is there. It was then that I focused my attention on whether there was a dependency conflict. I opened POP.xml and checked it with Dependency Analyzer, and sure enough, the Commons-Codec package I was using had a conflict.

In the Y module, the dependencies are Y -> B -> C -> commons-codec-1.10. In the X module, the A package is referenced: X -> A -> commons-codec-1.6, and the Y module is referenced: X -> Y -> B -> C -> Commons-codec-1.10. As you can see, the Commons-Codec package has two versions 1.6 and 1.10, so Maven does dependency mediation. The first rule is “shortest path first”, so only the 1.6 package will be used. The getSha256Digest method is private and the HmacUtils class does not exist. The previous error was explained. This conflict can be resolved by excluding dependencies, including commons-codec under package A, as follows:

<dependencies>
    <dependency>
        <groupId>com.chaycao.maven.dependency</groupId>
        <artifactId>A</artifactId>
        <version>1.0 the SNAPSHOT</version>
        <exclusions>
            <exclusion>
                <artifactId>commons-codec</artifactId>
                <groupId>commons-codec</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.chaycao.maven.dependency</groupId>
        <artifactId>Y</artifactId>
        <version>1.0 the SNAPSHOT</version>
    </dependency>
</dependencies>
Copy the code

After exclusion, only version 1.10 will be available and the program will run normally.

2. Why Maven relies on mediation

This is why dependency conflicts can cause NoClassDefFoundError, NoSuchMethodException, IllegalAccessError, etc. Although the Y module compiled properly due to the introduction of Commons-codec 1.10, it did not run properly due to dependency conflicts when only the 1.6 version of the package was loaded.

Note: Compilation of code simply compiles the current code. After a successful compilation, the final success depends on whether the runtime environment is equivalent to or compatible with the compile-time environment.

Let’s think about why Maven relies on mediation, and if not.

When using Maven, ifa package with the same groupId and artifactId but different version is introduced at the same time, Maven will consider a dependency conflict and will use dependency mediation to determine which version of the package to use: The first principle is that the closest path takes precedence, as described above. If the paths are the same, the second principle is used, with the first declarator taking precedence in the POM. When we hit Run, there will only be one explicit version of the package in the classpath.

Think about it. Whether Java can reference packages of different versions at runtime. The classpath parameter of a Java command can refer to packages of different versions. The classpath parameter tells the JVM how to search for a class file. If you specify multiple versions of a package in your classpath, the JVM will search for the class file in the JAR and load it. When a class with the same name is loaded, it will not be loaded again, that is, the same class will only be loaded once. This also means that when there are multiple packages of different versions, the order of the packages in the classpath determines which classes in the package are loaded first. And there is uncertainty. Because in a production environment it is common to concatenate JAR packages using shell commands:

LIB_DIR=lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"`
Copy the code

The order of the RESULTING JAR packages may be different in different environments. Maven dependency mediation avoids uncertainty by allowing only one clear version of the package to participate in the build.

Arthas: Arthas is a useful tool for troubleshooting online problems

Arthas, I’ve heard about it for a long time, but I haven’t used it yet. I tried it this time and I think it’s ok. For the dependency conflict situation described above, Arthas can directly view the DigestUtils at run time when IllegalAccessError is reported. We return the code to its original state and add an infinite loop to the Main class to keep the program alive for Arthas viewing.

public class Main {

    public static void main(String[] args) {
        while (true) {
            try {
                Encryptor encryptor = new Encryptor();
                String s = "1234567890";
                String result = encryptor.encrype(s);
                System.out.println(result);
            } catch (Throwable e) {

            }
        }
    }

}
Copy the code

Open Arthas, connect to our program (which can be learned through the official tutorial), and then view DigestUtils with sc command:

[arthas@32328]$ sc -d org.apache.commons.codec.digest.DigestUtilsclass-info org.apache.commons.codec.digest.DigestUtils code-source / D: / mavenrepo/Commons - codec/Commons - codec / 1.6 / Commons - codec - 1.6. The jar name org.apache.com mons. Codec. Digest. DigestUtils isInterface false isAnnotation false isEnum false isAnonymousClass false isArray false isLocalClass false isMemberClass false isPrimitive false isSynthetic false simple-name DigestUtils modifier public annotation interfaces super-class +-java.lang.Object class-loader +-sun.misc.Launcher$AppClassLoader@58644d46 +-sun.misc.Launcher$ExtClassLoader@24e74ca5 classLoaderHash 58644d46Copy the code

It is clear from code-source which package DigestUtils belongs to, and this is when you realize you have a dependency conflict problem.

Using JAD, you can decompile and view code online. Easy to use!

4. Reference

  1. Arthas helps you resolve dependency conflicts of the same name
  2. Maven dependency conflict problem principle analysis
  3. A fresh look at Jar package conflict issues and solutions