preface

Dependency conflicts are a common part of everyday development, and with luck, they don’t matter. Unfortunately, Ah fan is a little back, encountered several production problems, investigation all night, and finally found that the problem is caused by dependency conflict.

If you have not encountered this problem, you may not feel it. Ah Fan cites two recent examples to let you feel some.

Example 1:

Our company has an old business foundation package A. B and C services depend on this package. A team copied part of the code from A, refactored it with the same class name and path, and repackaged it as D.

When the test environment is running, everything is OK, but when the production run, NoSuchMethodError is raised.

The cause is that B’s services depend on A and D. There are two classes with the same name in A and D, and when running, the specific load of whom, different environments are really different.

Example 2:

A uses Dubbo to make RPC calls. Dubbo relies on JavAssist. The current dependencies are:

A -- -- -- -- -- -- -- > Dubbo -- -- -- -- -- -- -- > javassist - 3.18.1. GACopy the code

One of the changes introduced another third-party open source package that relies on Javassist-3.15.0-GA. When javassist-3.15.0-GA was packaged into the application for production release, it failed because production was JDK1.8.

In addition to the above problems, dependency conflicts can cause an application to throw ClassNotFoundException, NoClassDefFoundError, and other errors.

Throwing errors is a good case and makes it easier to locate the problem. Different versions of the same class have different internal logic, resulting in service exceptions. This kind of question, it’s really maddening, it’s head-scratcher.

                                                

A careful analysis of dependency conflicts can be divided into two main categories:

  • The project has the same dependent application, there are multiple versions, each version is the same class, may have differences.
  • The project depends on different applications. There are classes with the same package name and class name.

Let’s analyze the causes of dependency conflicts.

1 Causes of dependency conflicts

1.1 Dependency Mechanism

Maven dependencies are divided into two types, direct and indirect dependencies. This is easier to understand, you can just look at the diagram.



1.2 Arbitration Mechanism

If the A application indirectly depends on multiple C applications with different versions, Maven will use arbitration to select:

  • Neither of the following principles is valid when arbitrating in preference to the version declaration specified in the dependency management element
  • Short path preference
  • If the paths are the same, the order of declarations in the POM will be looked at.

The first principle, we’ll talk about next.

The second principle is as follows:



A is indirectly dependent on both versions of E, in which case e-1.0 will be used because A has the shortest path to e-1.0.

If the paths are exactly the same, then Maven has no choice but to choose the one declared first, based on the order in the POM.

1.3 the scope attribute

Maven projects can be divided into three phases: build phase, test phase, and run phase. Through the scope attribute, we can decide whether the dependent application participates in the above stages, which will also affect the dependency delivery.

Maven provides six scopes:

  • compile
  • provided
  • runtime
  • test
  • system
  • import

compile

Compile is Maven’s default property and will enable dependent packages to participate in the compile, test, and run phases of the project. Of course, this dependency will be included when the project is packaged.

provided

Provided means that a dependency only participates in the compilation, testing phase of the project. If the following dependencies exist:

A—–>B—–>C

The scope of C is provided. C will participate in the compilation and testing stages of B, but C will not be passed to A. If C is required by A running procedure, you need to introduce C dependencies directly yourself. A typical example is the Servlet API, as Tomcat and other containers provide it internally.

runtime

Runtime represents a dependency that is no longer involved in the compile phase of the project, but only in the test, run phase.

If the dependency does not participate in the compile phase, there is no way to import the corresponding class in the IDE. If there are dependent classes, an error will be reported during compilation.

Typical examples are JDBC driver packages such as mysql:

< the dependency > < groupId > mysql < / groupId > < artifactId > mysql connector - Java < / artifactId > < version > 6.0.6 < / version > <scope>runtime</scope> </dependency>Copy the code

The good thing is that you can only use the JDBC standard interface, so you are not bound to a specific database. If you want to change the database, you only need to replace the POM and modify related parameters.

test

Test is only involved in the test phase. A typical example is junit:

<dependency>     <groupId>junit</groupId>     <artifactId>junit</artifactId>     <version>4.12</version>     <scope>test</scope> </dependency> Copy the code

 system

System has the same scope as provided, except that System needs to specify the local path using the systemPath attribute, and provided will be pulled from the Maven repository.

import

Import is special and will not participate in the above phases. This can only be used under dependencyManagement and type must be POM. A typical example is the Spring-boot dependency.

  1. <dependencyManagement> <dependencies> <! -- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> The < artifactId > spring - the boot - dependencies < / artifactId > < version > 2.1.6. RELEASE < / version > <type>pom</type> 
                <scope>import</scope> 
            </dependency> 
        </dependencies> 
    </dependencyManagement> Copy the code

This way, you can solve the single inheritance problem and better classify dependencies.

Also, Maven Scope will affect dependency delivery.



If the dependency is A– >B– >C,A depends on B, and B depends on C. The leftmost column represents the scope attribute of B, and the first line represents the scope attribute of C

As shown above, when C’s scope is provided/test, C only plays A role in B and is not passed to A through indirect dependency.

If and only if B’s scope is compile and C’s scope is Runtime, A will indirectly depend on C whose scope is Runtime. In other cases, C’s scope will be consistent with B’s scope.

2. Ways to resolve conflicts

2.1 Use Maven properties to control dependency delivery

If it depends on conflict, locate the conflict class according to the error log, locate the corresponding JAR package, and exclude the corresponding package through the excludes family.

In addition, the IDEA Maven Helper plug-in can be used to proactively check for conflicting dependencies and eliminate them in advance.



Through the plug-in, we can clearly see the conflicting packages, the dependent paths, and the corresponding scopes.

In addition to excluding dependencies, we can prevent dependency propagation by properly setting the scope property. For example, A needs to use some class in the Spring-Beans package. If other projects are bound to use Spring, we can set the Spring-BeansScope in A to provided and let the other projects choose to introduce the version of Spring-Beans.

This is suitable for common base packages. Do not use provided for other packages. If you do use it, be sure to specify the dependencies that need to be introduced during use.

Although the above methods treat the symptoms, but not the root cause. If we want to avoid dependency conflicts, we need to establish certain norms in advance, and the team will abide by them, so as to effectively avoid such problems.

  1. DependencyManagement is used in the application project to manage the base dependencies and define the same version, such as the common intermediate package, toolkit, log package.
  2. Do not introduce extraneous dependencies in binary packages, so as to minimize dependencies. In team development, it is common for a binary package to inherit from a common parent POM, resulting in many unrelated dependencies that can be managed separately.
  3. Do not change the existing class name, method name, field name.
  4. Before the project application goes live, replace snapshot with the official version. Although snapshot is easy to modify, it can be modified at will because of this feature. If a production package release is not careful, it will be introduced.
  5. Do not use the same package name or class name for two packages. Generally speaking, in team development, package names and class names are less likely to be the same. This kind of comparison can easily occur in some refactoring projects, where the original class is copied, refactoring is packaged and distributed. You can change the package name in this case. Such as cmomon – lang3 is common – upgraded version, lang cmomon – lang3 package called org.apache.com mons. Lang3, and common – lang package called org.apache.com mons. Lang

3. Summary

If we think of the NPE problem as the neophyte monster, then the dependency conflict problem is the elite monster of the Centaur. At first, we’re gonna get beat up pretty bad. Only if we keep upgrading and learning the skills, can we take our time.

Ps: In Zelda, how many times did you first meet centaurs? I remember playing from 9 p.m. to 2 a.m., but I couldn’t beat him



4. Help documents

Maven Dependency Scopes

Maven Optional keyword thoroughly illustrated

The above content is some of my own feelings, share out welcome correction, incidentally beg a wave of attention

Author: Duck blood fan


Source:
Java geek technology