Maven处理依赖冲突

不知道你在使用Maven时是否遇到过诸如"NoSuchMethodError"或"ClassNotFoundException"之类的问题,甚至发生这些问题的Java类你没都没有听说过。要搞清楚这里面的缘由,我们得学习Maven对依赖冲突的处理机制。

 

Maven采用“最近获胜策略(nearest wins strategy)”的方式处理依赖冲突,即如果一个项目最终依赖于相同artifact的多个版本,在依赖树中离项目最近的那个版本将被使用。让我们来看看一个实际的例子。

 

请下载本文的github源代码:https://github.com/davenkin/maven-dependency-conflict-demo

 

我们有一个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()两个方法。整个项目的依赖关系如下图:

 

Maven处理依赖冲突


根据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的依赖关系:


[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中的步骤重现该错误信息。)


解决办法

对于这种有依赖冲突所导致的问题,我们有两种解决方法。


方法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声明:


       <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",结果如下:


......

[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的依赖(方法一)。





在eclipse中查看:


Maven处理依赖冲突


Maven的Pom文件中的隐式依赖导致Jar包冲突的问题


在一次的maven项目中遇到这样一个bug:

    编译器没有报什么错,但无法编译,或者能编译,项目启动不了。后来我才发现是以下的问题:

    项目中的pom文件中,依赖了webx3.core,而webx3.core又隐式依赖了fasttext相关的jar包,同时我在pom中也引人了fasttext.all,

    fasttext.all也隐式依赖了fasttext相关的jar包,两类jar包版本还不一样,这样就导致了jar包冲突的问题,牵扯到的pom文件依赖如下:

<dependency>
<groupId>com.alibaba.platform.shared</groupId>
<artifactId>webx3.core</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.platform.shared</groupId>
<artifactId>fasttext.all</artifactId>
</dependency>

那怎么查看这种jar包冲突问题呢?

eclipse提供了对隐式依赖jar包的查看功能:在eclipse中打开一个pom文件,在Dependency Hierarchy的Tab页中,就可以查看当前pom文件中显示声明的jar包,及这些显示声明的jar中隐式引入的依赖jar包,我在这里搜索fasttext,就可以清楚的看到了,一个依赖了1.3.5版本的,一个依赖了1.3-SNAPSHOT版本的。Maven处理依赖冲突

    发现了问题就简单了,只要通过排除webx3.core中的fasttext的依赖,就解决了上述冲突的问题:
<dependency>
<groupId>com.alibaba.platform.shared</groupId>
<artifactId>webx3.core</artifactId>
<exclusions>
<exclusion>
<artifactId>jakarta.commons.codec</artifactId>
<groupId>com.alibaba.external</groupId>
</exclusion>
<exclusion>
<groupId>com.alibaba.platform.shared</groupId>
<artifactId>fasttext-css</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba.platform.shared</groupId>
<artifactId>fasttext-psoriasis</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba.platform.shared</groupId>
<artifactId>fasttext-sec</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba.platform.shared</groupId>
<artifactId>fasttext-segment</artifactId>
</exclusion>
</exclusions>
</dependency>
对应排除,改bug是个挺实用的方法。