This article is part of the Maven source Code Parsing series: How does dependency mediation work? The third chapter mainly introduces the first principle of dependency mediation: transfer dependency, the path nearest priority. This article is more content, but also to start the first source analysis, please be sure to read carefully, otherwise the following article may not understand. The main table of contents for the series of articles is: juejin.cn/post/703292…

scenario

A has the following dependencies: A- > B- > C- > X (1.0), A- > D- > X (2.0). X is A transitive dependency of A, but there are two versions of X on both dependency paths. It would obviously not be right for both versions to be resolved because that would create dependency duplication, so you have to choose one. In this example, the length of X (1.0) is 3 and that of X (2.0) is 2, so X (2.0) is parsed.

The pom.xml of A reads as follows:

<? The XML version = "1.0" encoding = "utf-8"? > < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < the parent > The < artifactId > mavenDependencyDemo < / artifactId > < groupId > org. Example < / groupId > < version > 1.0 < / version > < / parent > <modelVersion>4.0.0</modelVersion> <artifactId>A</artifactId> <version>1.0</version> <dependencies> <groupId>org.example</groupId> <artifactId>B</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>D</artifactId> <version>1.0</version> </dependency> </dependencies> </project>Copy the code

The pom.xml of B reads as follows:

<? The XML version = "1.0" encoding = "utf-8"? > < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < the parent > The < artifactId > mavenDependencyDemo < / artifactId > < groupId > org. Example < / groupId > < version > 1.0 < / version > < / parent > <modelVersion>4.0.0</modelVersion> <artifactId>B</artifactId> <version>1.0</version> <dependencies> <groupId>org.example</groupId> <artifactId>C</artifactId> <version>1.0</version> </dependency> </dependencies> </project>Copy the code

The pom.xml content of C is as follows:

<? The XML version = "1.0" encoding = "utf-8"? > < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < the parent > The < artifactId > mavenDependencyDemo < / artifactId > < groupId > org. Example < / groupId > < version > 1.0 < / version > < / parent > <modelVersion>4.0.0</modelVersion> <artifactId>C</artifactId> <version>1.0</version> <dependencies> <groupId>org.example</groupId> <artifactId>X</artifactId> <version>1.0</version> </dependency> </dependencies> </project>Copy the code

The pom.xml of D is as follows:

<? The XML version = "1.0" encoding = "utf-8"? > < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < the parent > The < artifactId > mavenDependencyDemo < / artifactId > < groupId > org. Example < / groupId > < version > 1.0 < / version > < / parent > <modelVersion>4.0.0</modelVersion> <artifactId>D</artifactId> <version>1.0</version> <dependencies> <groupId>org.example</groupId> <artifactId>X</artifactId> <version>2.0</version> </dependency> </dependencies> </project>Copy the code

The source code

Just got the source code and don’t know where to break it, we can switch to module A and execute this command:

 mvn dependency:tree -Dverbose
Copy the code

Verbose is used to output detailed information to help us find reference points in the source code.

It can be found that the output is:

[INFO] Scanning for projects... [the INFO] [INFO] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- < org. Example: A > -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- [INFO] Building A 1.0 [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- Maven-dependency plugin:2.8:tree (default-cli) @a -- [INFO] org.example:A:jar:1.0 [INFO] +- Org. Example: B: jar: 1.0: the compile | \ [INFO] - org. Example: C: jar: 1.0: the compile | \ [INFO] - (org. Example: X: jar: 1.0: the compile - Omitted for conflict with 2.0) \ [INFO] - org. Example: D: jar: 1.0: the compile \ [INFO] - org. Example: X: jar: 2.0: the compile/INFO ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- [INFO] Total time: 1.002 s [INFO] Finished at: 2021-11-20T12:17:29+08:00 [INFO] ------------------------------------------------------------------------Copy the code

As you can see, A relies on X (2.0) and X (1.0) is ignored. The take-home message is

(org. Example: X: jar: 1.0: the compile - omitted for conflict with 2.0)Copy the code

Maven: Apache-maven-3.6.3, maven-dependency plugin, and maven-dependency tree: apache-maven-3.6.3, maven-dependency plugin, and maven-dependency tree

We finally found the source of this output in the Maven-dependency tree plug-in project:

At this point, we can use the debugging method described earlier (note the debugging plugin Maven-dependency tree) and break the point here to find the call chain:

This TreeMojo is clearly in the Maven-dependency plugin project:

Combination in the maven – dependency – tree in front of the invocation chain, it is in TreeMojo# serializeVerboseDependencyTree this method, serialization dependencies in the visitor way, In this example, use the visitor is BuildingDependencyNodeVisitor, can return to the maven dependency – tree project view invocation chain to confirm:

Once this step is clear, let’s move on to the point where Maven-dependency tree broke off to see why we got there and what context there is.

We see that the focus is on the state field, so look where it is assigned a value.

OMITTED_FOR_CONFLICT = OMITTED_FOR_CONFLICT = OMITTED_FOR_CONFLICT = OMITTED_FOR_CONFLICT

Incidentally, you can see:

If the versions of duplicate dependencies are the same, state is OMMITTED_FOR_DUPLICATE, which means duplicate.

If the admitted version numbers are different then state is omadmitted _FOR_CONFLICT and you must only choose one of them.

Continue up to find the call chain:

We find that the method omitForcompetitive is defined in the ResolutionListener class in the core project apapache – Maven-3.6.3. The plugin maven dependency – DependencyTreeResolutionListener is responsible for the specific implementation of the tree.

So we interrupt the debugging, and in omittedNode. OmitForConflict (kept) here to make a breakpoint, debug, to see what the context has:

As you can see, the dependencies to be ignored and the dependencies to be preserved are passed in from above. That is, the core apache-Maven-3.6.3 project has made a judgment call about which dependency to keep.

So we broke debugging again and went back to the apache-Maven-3.6.3 core project to re-debug.

40. I have lunch with my wife. 40. I have lunch with my wife.

After the breakpoint comes in, we follow the call chain to see where the decision was made:

As you can see, in the org. Apache.. Maven repository. Legacy. Resolver. DefaultLegacyArtifactCollector# recurse this method. It seems that the key method is to checkScopeUpdate. We need to click on it to see which dependencies are ignored and which are retained.

Let’s debug again. For convenience, we can set conditional breakpoint “X”.equals(((DefaultArtifact) neare.artifact).artifactid) and only care about X dependencies.

It can be seen that this method is only processed according to scope priority, and in a word, the dependencies with higher priority are reserved. But that’s not our scenario.

So, we should go up the call chain.

Redebugging (or going back to the call stack, then forward) will find it here:

It can be seen that nearest comes from node, and aposis comes from previous. And this assignment is controlled by the equivalence between Resolved and Previous. Let’s take a look at the source of Previous, Resolved, and Node.

X (1.0) and node (2.0) are both parsed in the previous step:

And resolved here:

Combined with the above analysis, we can know that:

If Resolved is the same as Previous, leave previous and ignore Node; Otherwise, keep Node and ignore previous.

We need to see what’s going on inside this line of code:

resolved = conflictResolver.resolveConflict( previous, node );
Copy the code

Debug entry:

You can see that we are entering the NearestConflictResolver conflict mediator, which specifically selects the nearest dependency to the path. From the implementation level, it’s pretty simple: it directly compares the two dependent path depths and finds that X (1.0) has a depth of 3 and X (2.0) has a depth of 2, and as a rule, the smaller path depth X (2.0) needs to be preserved.

So the question comes, when will call conflictResolver. ResolveConflict (previous, node)? See below:

After this step, X (2.0) is added to previousNodes as well:

X (1.0) and node (2.0) are previous and previous versions of X (1.0) and node (2.0).

It’s easy to see that the dependency resolution process is a deep walk, where recurse methods are recursed over and over again. Using our example, we iterate through A- > B- > C- > X (1.0), then A- > D- > X (2.0), and the process we just debugged is at the point where D- > X (2.0) has just completed.

Ok, when the end of the recursive traversal, back to the org.. Apache maven. Repository. Legacy. Resolver. DefaultLegacyArtifactCollector# collect method, prepare to generate results:

As you can see, only isActive’s dependencies are collected in the result, which is the version of the dependency that ultimately works. In fact, the active setting is implemented in the previous step:

As you can see, if a dependency is ignored, all of its own dependencies are also ignored.

summary

So far, we have seen how the path nearest first principle works: dependency resolution is a deep traversal process. Each dependency resolution is placed in the resolvedArtifacts Map, and subsequent conflict mediation is performed when dependencies with the same name are seen. For the path nearest first principle, the specific conflict mediator is NearestConflictResolver.

ConflictResolver is a ConflictResolver interface that implements dependency mediation. The ConflictResolver interface is a ConflictResolver interface that implements dependency mediation. For the nearest path first rule, it is handled by NearestConflictResolver. Will other principles be handled by other dependency mediators?

Let’s look at some specific dependency mediators:

As you can see from the above source code, there are four types of mediators:

  • The oldest version is preferred
  • The latest version is preferred
  • Path nearest takes precedence (there is also a default mediator that inherits it, but the implementation is empty, marked @deprecated and can be ignored)
  • The furthest path is preferred

Returning to the mediation process, you can see that the default mediator is “shortest path first” :

So it’s safe to assume that the rest of the principles in this article do not use other mediators, and that they work in some plug-in. Maven has a plugin that can update versions to the latest. It is not used in this article.