maven之Transitive dependencies(默认树的广度优先遍历算法处理依赖冲突)
One of Maven's major contributions is the way itdeals and manages not onlydirect dependencies, but alsotransitive 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 higherdepth. 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 asdependency mediation.
其实就是数据结构中,树的
广度优先
遍历算法。
Let's consider an example. The following diagram shows a dependency tree:
Here is the corresponding dependencies block in POM:
directory
apacheds-core
0.9.3
org.apache.camel
camel-exec
2.9.7
org.apache.tapestry
tapestry-upload
5.3.7
com.googlecode.grep4j
grep4j
1.7.5
commons-io
commons-io
2.3
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 fromlevel 1. The dependency tree shown in the following diagram:
All dependencies tocommons-ioare oflevel 2, and differ on the versions:0.9.3 (viaapacheds-core),1.4(via camel-exec),2.0.1 (viatapestry-upload), and2.4 (viagrep4j). Unlike a popular belief, the resolution willnotlead 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 beforeapacheds-core, its embed version of commons-iowould have been resolved instead of version0.9.3. This case illustrates the concept offirst 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 tag, in order to break the transitive
dependency.
The POM will look similar to the following:
junit
junit
${junit.version}
test
org.dbunit
dbunit
${dbunit.version}
test
junit
junit
commons-collections
commons-collections
commons-collections
commons-collections
${commons-collections.version}
junit
junit
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 onexclusion 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:
4.0.0
com.packt.maven.dependency.optional
back
1.0-SNAPSHOT
Example of POM which is depended on with 'optional' at true
jar
• For project middle, the POM is as follows:
4.0.0
com.packt.maven.dependency.optional
middle
1.0-SNAPSHOT
Example of POM with an optional dependency
jar
com.packt.maven.dependency.optional
back
1.0-SNAPSHOT
true
• For project front, the POM is as follows:
4.0.0
com.packt.maven.dependency.optional
front
1.0-SNAPSHOT
Example of POM with scope import dependencyManagement of two artifacts with a version conflict because of transitive dependencies
jar
com.packt.maven.dependency.optional
middle
1.0-SNAPSHOT
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 thedependingproject or by the one on which it is depended on.
读书笔记:
Apache Maven Dependency Management
Copyright © 2013 Packt Publishing