maven之Transitive dependencies(默认树的先序遍历算法处理依赖冲突)

Posted A_Beaver

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了maven之Transitive dependencies(默认树的先序遍历算法处理依赖冲突)相关的知识,希望对你有一定的参考价值。

maven之Transitive dependencies(默认树的先序遍历算法处理依赖冲突)



One of Maven‘s major contributions is the way it deals and manages not only direct dependencies, but also transitive ones.
      你项目中的依赖,不管是直接依赖还是间接/传递依赖,maven都能很好的管理。

The concept of transitivity

Dependencies are transitive. This means that if A depends on B and B depends on C, then A depends on both B and C. Theoretically, there is no limit to the depth of dependency. So, if you observe the following diagram of the tree of dependencies,
you will notice that by transitivity, A depends on B, C, D, … until Z:

技术分享

Even worse, we could have added a bit of complexity in mixing different versions of the same artifacts. In this very example with A as root project, B and C are level 1 or direct dependencies, D, E, J, and F are level 2 dependencies, C, G, H, and K are level 3, and so on.

You can imagine that the greater the level of dependencies, the more complex the situation is. The underlying issue of transitivity, you may guess, is when dependencies bear on the same groupId/artifactId but with different versions.

Resolution

Maven carries out the following algorithm to choose between two different versions:
? Nearest first: A dependency of lower level has priority over another of the higher depth. Hence, a direct dependency has priority over a transitive dependency.
? First found: At the same level, the first dependency that is found is taken.

This algorithm is known as dependency mediation.

其实就是数据结构中,树的先序遍历算法。

Let‘s consider an example. The following diagram shows a dependency tree:

技术分享


Here is the corresponding dependencies block in POM:


<dependencies>
    <dependency>
        <groupId>directory</groupId>
        <artifactId>apacheds-core</artifactId>
        <version>0.9.3</version>
        <!--implicit dependency to commons-io:
commons-io:1.0-->
    </dependency>
    <dependency>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-exec</artifactId>
        <version>2.9.7</version>
        <!--implicit dependency to commons-io:commons-io:
1.4-->
    </dependency>
    <dependency>
        <groupId>org.apache.tapestry</groupId>
        <artifactId>tapestry-upload</artifactId>
        <version>5.3.7</version>
        <!--implicit dependency to commons-io:
commons-io:2.0.1-->
    </dependency>
    <dependency>
        <groupId>com.googlecode.grep4j</groupId>
        <artifactId>grep4j</artifactId>
        <version>1.7.5</version>
        <!--implicit dependency to commons-io:commons-io:
2.4-->
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.3</version>
    </dependency>
</dependencies>

The commons-io-2.3 dependency is a dependency of level 1. So, even though it is declared after other artifacts and their transitive dependencies, then the dependency mediation will resolve commons-io to version 2.3. This case illustrates the concept of nearest first.

Now let‘s compare to a POM for which commons-io-2.3 has been deleted from level 1. The dependency tree shown in the following diagram:

技术分享


All dependencies to commons-io are of level 2, and differ on the versions: 0.9.3 (via apacheds-core), 1.4 (via camel-exec), 2.0.1 (via tapestry-upload), and 2.4 (via grep4j). Unlike a popular belief, the resolution will not lead to take the greatest
version number (that is,
2.4), but the first transitive version that appears in the dependency tree, in other terms 0.9.3.


Had another dependency been declared before apacheds-core, its embed version of commons-io would have been resolved instead of version 0.9.3. This case illustrates the concept of first found.


Exclusions

Let‘s consider the following example:


技术分享



Our project needs JUnit-4.11, as well as DBUnit-2.4.9 and commonscollections-2.1. But the two latter depend on other versions of JUnit, respectively 2.3.0 and 3.2. Moreover, commons-collections depends on JUnit-3.8.1. Therefore, on building the project with goal test, we may encounter strange behaviors.
In this situation, you have to use an <exclusion> tag, in order to break the transitive
dependency
.
The POM will look similar to the following:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.dbunit</groupId>
        <artifactId>dbunit</artifactId>
        <version>${dbunit.version}</version>
        <scope>test</scope>
        <exclusions>
            <!--Exclude transitive dependency to
JUnit-3.8.2 -->
            <exclusion>
                <artifactId>junit</artifactId>
                <groupId>junit</groupId>
            </exclusion>
            <!--Exclude transitive dependency to
Commons-Collections-3.1-->
            <exclusion>
                <artifactId>commons-collections</artifactId>
                <groupId>commons-collections</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>${commons-collections.version}</version>
        <exclusions>
            <!--Exclude transitive dependency to
JUnit-3.8.1 -->
            <exclusion>
                <artifactId>junit</artifactId>
                <groupId>junit</groupId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>


Most of the time, you will choose to exclude a transitive dependency for one of the
following reasons:

? Conflicts between versions of the same artifact of your project, such as preceding version.
? Conflicts between artifacts of your project and artifacts from the platform of deployment, such as Tomcat or another server. For instance, if your project depends on wsdl4j-1.5.3 and is deployed on JBoss AS 7.1.1, then a conflict may appear with JBoss‘s dependency to wsdl4j-1.6.2.
? In some cases, you do not want some of your dependencies to be exported within the archive you build (even though in this case, using a play on the dependency scope should be more elegant). The opposite case (when you need use your own dependencies and ignore the similar artifacts bundled with the server) will be exposed in Chapter 6, Release and Distribute.


Optional dependencies


The previous mechanism, based on exclusion tag, is in charge of the depending project to exclude unwanted dependencies.
Another mean exists to exclude transitive dependencies. This time, the charge lies on the project on which it is depended on. Maven provides the optional tag that takes a boolean value (true/false).
Let‘s consider the following example of dependencies:

技术分享

Here are the corresponding POMs:
? For project back, the POM is as follows:

<?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">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.packt.maven.dependency.optional</groupId>
    <artifactId>back</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Example of POM which is depended on with 'optional' at true</name>
    <packaging>jar</packaging>
    <!-- no dependency at all -->
</project>


? For project middle, the POM is as follows:

<?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">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.packt.maven.dependency.optional</groupId>
    <artifactId>middle</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Example of POM with an optional dependency</name>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>com.packt.maven.dependency.optional</groupId>
            <artifactId>back</artifactId>
            <version>1.0-SNAPSHOT</version>
            <!-- The dependency to artifact 'back'
is set at optional-->
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>


? For project front, the POM is as follows:

<?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">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.packt.maven.dependency.optional</groupId>
    <artifactId>front</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Example of POM with scope import dependencyManagement of two artifacts with a version conflict because of transitive dependencies</name>
    <packaging>jar</packaging>
    <dependencies>
        <!-- Regular dependency ; 'front' depends
on 'middle'-->
        <dependency>
            <groupId>com.packt.maven.dependency.optional</groupId>
            <artifactId>middle</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>


Now, we will see how to display the dependency trees. For middle, the tree is not different from what it would be, had the optional tag been set at false:
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ middle ---
[INFO] com.packt.maven.dependency.optional:middle:jar:1.0-SNAPSHOT
[INFO] \- com.packt.maven.dependency.optional:back:jar:1.0-
SNAPSHOT:compile

But for front, we get the following output:
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ front ---
[INFO] com.packt.maven.dependency.optional:front:jar:1.0-SNAPSHOT
[INFO] \- com.packt.maven.dependency.optional:middle:jar:1.0-
SNAPSHOT:compile


In other terms, middle has prevented its dependency to back to propagate transitively to other projects that depend on middle (among which front; but middle has no idea of front).
Had we removed the optional tag, we would have got that other trace:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ front ---
[INFO] com.packt.maven.dependency.optional:front:jar:1.0-SNAPSHOT
[INFO] \- com.packt.maven.dependency.optional:middle:jar:1.0-
SNAPSHOT:compile
[INFO] \- com.packt.maven.dependency.optional:back:jar:1.0-
SNAPSHOT:compile


As a conclusion, exclusions and optional allow to break the chain of transitivity. This may be driven either by the depending project or by the one on which it is depended on.



读书笔记:
Apache Maven Dependency Management
Copyright ? 2013 Packt Publishing









































































































以上是关于maven之Transitive dependencies(默认树的先序遍历算法处理依赖冲突)的主要内容,如果未能解决你的问题,请参考以下文章

Maven: Transitive Dependencies

scalac 错误:错误选项:通过命令行在 mvn 包上执行“-make:transitive”

Gradle-5.3:依赖-管理依赖的版本(传递(transitive)排除(exclude)强制(force)动态版本(+))

Apache IVY 到 Maven:排除工件的错误?

Nexus Ivy Maven:二级依赖忽略传递

在 Maven 中跟踪托管依赖项版本