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