不知道你在使用Maven時是否遇到過諸如"NoSuchMethodError"或"ClassNotFoundException"之類的問題,甚至發生這些問題的Java類你沒都沒有據說過。要搞清楚這裏面的原因,咱們得學習Maven對依賴衝突的處理機制。java
Maven採用「最近獲勝策略(nearest wins strategy)」的方式處理依賴衝突,即若是一個項目最終依賴於相同artifact的多個版本,在依賴樹中離項目最近的那個版本將被使用。讓咱們來看看一個實際的例子。git
請下載本文的github源代碼:https://github.com/davenkin/maven-dependency-conflict-demogithub
咱們有一個web應用resolve-web,該工程依賴於project-A和project-B,project-A依賴於project-common的1.0版本並調用其中的sayHello()方法。project-B依賴於project-C,而project-C又進一步依賴於project-common的2.0版本並調用其中的sayGoodBye()方法。project-common的1.0和2.0版本是不一樣的,1.0中之包含sayHello()方法,而2.0中包含了sayHello()和sayGoodBye()兩個方法。整個項目的依賴關係以下圖:web
根據Maven的transitive依賴機制,resolve-web將同時依賴於project-common的1.0和2.0版本,這就形成了依賴衝突。而根據最近獲勝策略,Maven將選擇project-common的1.0版本做爲最終的依賴。這和Gradle不一樣,Gradle在默認狀況下將選擇最新的版本做爲獲勝版本。而對於Maven,因爲proejct-common的1.0版本比2.0版本在依賴樹中離resolve-web更近,故1.0版本獲勝。在resolve-web中執行"mvn dependency:tree -Dverbose"能夠看到resolve-web的依賴關係:api
[INFO] resolve-web:resolve-web:war:1.0-SNAPSHOT [INFO] +- junit:junit:jar:3.8.1:test [INFO] +- project-B:project-B:jar:1.0:compile [INFO] | \- project-C:project-C:jar:1.0:compile [INFO] | \- (project-common:project-commmon:jar:2.0:compile - omitted for conflict with 1.0) [INFO] +- project-A:project-A:jar:1.0:compile [INFO] | \- project-common:project-commmon:jar:1.0:compile [INFO] \- javax.servlet:servlet-api:jar:2.4:provided
由上可知,project-common:project-commmon:jar:2.0被忽略掉了。此時在resolve-web的war包中將只包含project-common的1.0版本,因而問題來了。因爲project-common的1.0版本中不包含sayGoodBye()方法,而該方法正是project-C所須要的,因此運行時將出現「NoSuchMethodError」。(請根據本文github工程中的README.md中的步驟重現該錯誤信息。)maven
對於這種有依賴衝突所致使的問題,咱們有兩種解決方法。ide
方法1:顯式加入對project-common 2.0版本的依賴。先前的2.0版本不是離resolve-web遠了點嗎,那咱們就直接將它做爲resolve-web的依賴,這不就比1.0版本離resolve-web還近嗎?在resove-web的pom.xml文件中直接加上對project-common 2.0 的依賴:學習
<dependency> <groupId>project-common</groupId> <artifactId>project-commmon</artifactId> <version>2.0</version> </dependency>
方法2:resolve-web對project-A的dependency聲明中,將project-common排除掉。在resolve-web的pom.xml文件中修改對project-A的dependency聲明:rest
<dependency> <groupId>project-A</groupId> <artifactId>project-A</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>project-common</groupId> <artifactId>project-commmon</artifactId> </exclusion> </exclusions> </dependency>
此時再在resolve-web中執行"mvn dependency:tree -Dverbose",結果以下:code
[INFO] resolve-web:resolve-web:war:1.0-SNAPSHOT [INFO] +- junit:junit:jar:3.8.1:test [INFO] +- project-B:project-B:jar:1.0:compile [INFO] | \- project-C:project-C:jar:1.0:compile [INFO] | \- project-common:project-commmon:jar:2.0:compile [INFO] +- project-A:project-A:jar:1.0:compile [INFO] \- javax.servlet:servlet-api:jar:2.4:provided
此時的依賴樹中已經不包含project-common的1.0版本了。
另外,咱們還能夠在project-A中將對project-common的依賴聲明爲optional,optional即表示非transitive,此時當在resolve-web中引用project-A時,Maven並不會將project-common做爲transitive依賴自動加入,除非有別的項目(好比project-B)聲明瞭對project-common的transitive依賴或者咱們在resolve-web中顯式聲明對project-common的依賴(方法一)。