maven冲突管理及依赖管理实践

1、“最近获胜策略(nearest wins strategy)”的方式处理依赖冲突

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

我们有一个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的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版本获胜。project-common:project-commmon:jar:2.0被忽略掉了。此时在resolve-web的war包中将只包含project-common的1.0版本,于是问题来了。由于project-common的1.0版本中不包含sayGoodBye()方法,而该方法正是project-C所需要的,所以运行时将出现“NoSuchMethodError”。

2、依赖冲突的解决方法

方法1:在最近pom文件中显式加入依赖。先前的2.0版本不是离resolve-web远了点吗,那我们就直接将它作为resolve-web的依赖,这不就比1.0版本离resolve-web还近吗?在resove-web的pom.xml文件中直接加上对project-common 2.0 的依赖
方法2:在pom文件中引入依赖时排除多冲突jar包的依赖,如在resolve-web对project-A的dependency声明中,将project-common排除掉。

3、Maven的默认插件

Maven就其本身来说只是提供一个执行环境,它并不知道需要在项目上完成什么操作,真正操作项目的是插件(plugin),比如编译Java有Compiler插件,打包有Jar插件等。所以要让Maven完成各种各样的任务,我们需要配置不同的插件,甚至自己编写插件。你可能要问了:“我并没有配置什么插件啊,照样能编译打包。”这是因为Maven在默认情况下已经给我们配置了一些常用的插件,上面的Compiler和Jar便在这些插件之列。要查看Maven的默认插件,我们需要找到Super Pom。就像Java中的类隐式继承Object一样,对于pom.xml来说,它隐式继承超级POM。针对Maven3来说,该超级POM位于maven-model-builder-VERSION.jar包中(该jar包位于maven根目录/lib下)

解压该jar包,可以在maven-model-builder-VERSION/org/apache/maven/model目录中找到pom-4.0.0.xml,即超级POM。

4、依赖范围

主要的依赖范围以及作用:

 

依赖范围(scope) 主源码classpath可用 测试源码classpath可用 会被打包
compile 缺省值 TRUE TRUE TRUE
test FALSE TRUE FALSE
runtime FALSE TRUE TRUE
provided TRUE TRUE FALSE

5、分类器

GAV是Maven坐标最基本最重要的组成部分,但GAV不是全部。还有一个元素叫做分类器(classifier),90%的情况你不会用到它,但有些时候,分类器非常不可或缺。

举个简单的例子,当我们需要依赖TestNG的时候,简单的声明GAV会出错,因为TestNG强制需要你提供分类器,以区别jdk14和jdk15,我们需要这样声明对TestNG的依赖:

Xml代码  收藏代码
  1. <dependency>  
  2.   <groupId>org.testng</groupId>  
  3.   <artifactId>testng</artifactId>  
  4.   <version>5.7</version>  
  5.   <classifier>jdk15</classifier>  
  6. </dependency>  

你会注意到maven下载了一个名为testng-5.7-jdk15.jar的文件。其命名模式实际上是<artifactId>-<version>-<classifier>.<packaging>。理解了这个模式以后,你就会发现很多文件其实都是默认构件的分类器扩展,如 myapp-1.0-test.jar, myapp-1.0-sources.jar。

分类器还有一个非常有用的用途是:我们可以用它来声明对test构件的依赖,比如,我们在一个核心模块的src/test/java中声明了一些基础类,然后我们发现这些测试基础类对于很多其它模块的测试类都有用。没有分类器,我们是没有办法去依赖src/test/java中的内容的,因为这些内容不会被打包到主构件中,它们单独的被打包成一个模式为<artifactId>-<version>-test.jar的文件。

我们可以使用分类器来依赖这样的test构件:

Xml代码  收藏代码
  1. <dependency>  
  2.   <groupId>org.myorg.myapp</groupId>  
  3.   <artifactId>core</artifactId>  
  4.   <version>${project.version}</version>  
  5.   <classifier>test</classifier>  
  6. </dependency>  

理解了分类器,那么可供依赖的资源就变得更加丰富。

6、多模块依赖管理

当你只有一个Maven模块的时候,你完全不需要看这个部分。但你心里应该清楚,只有一个Maven模块的项目基本上只是个玩具。

实际的项目中,你会有一大把的Maven模块,而且你往往发现这些模块有很多依赖是完全项目的,A模块有个对spring的依赖,B模块也有,它们的依赖配置一模一样,同样的groupId, artifactId, version,或者还有exclusions, classifer。细心的分会发现这是一种重复,重复就意味着潜在的问题,Maven提供的dependencyManagement就是用来消除这种重复的。

正确的做法是:

1. 在父模块中使用dependencyManagement配置依赖

2. 在子模块中使用dependencies添加依赖

dependencyManagement实际上不会真正引入任何依赖,dependencies才会。但是,当父模块中配置了某个依赖之后,子模块只需使用简单groupId和artifactId就能自动继承相应的父模块依赖配置。

Maven提高篇系列之(五)——处理依赖冲突 - 无知者云 - 博客园

Maven最佳实践:管理依赖

posted on 2017-08-25 08:28  时间朋友  阅读(792)  评论(0编辑  收藏  举报

导航