Maven如何解决包冲突
Maven是一个跨平台的项目管理工具。作为Apache组织的一个颇为成功的开源项目,其主要服务于基于Java平台的项目创建,依赖管理和项目信息管理。
Maven依赖配置
maven的依赖配置主要是如下格式:
1 |
|
根元素下project下的dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:
- groupId,artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到需要的依赖。
- type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值是jar。
- scope:依赖的范围,下面会进行详解。
- optional:标记依赖是否可选。
- exclusions:用来排除传递性依赖
依赖范围
Maven在编译主代码的时候需要使用一套classpath,在编译和执行测试的时候会使用另一套classpath,实际运行项目的时候,又会使用一套classpath。
依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:
- compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译,测试和运行的时候都需要使用该依赖。
- provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。
- test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子就是JUnit,它只有在编译测试代码及运行测试的时候才需要。
- runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
- system:系统依赖范围。该依赖范围与provided所表示的依赖范围一致,对于编译和测试classpath有效,但在运行时无效。只是使用system范围依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用,systemPath元素可以引用环境变量。
依赖传递
当我们项目依赖了Jar包A,而Jar包A又依赖了Jar包B和Jar包C,那么maven在引入的时候就会把A依赖的包B和C也一并引入进来。
包冲突产生的原因
假设项目A引用了Jar包B和Jar包C,Jar包B又引用了Jar包D的1.0版本,而Jar包C引用了Jar包D的2.0版本。这个时候,因为依赖传递,Maven会引入Jar包D的1.0和2.0版本,这个时候就产生了包冲突。
Maven解决包冲突的措施
在某些场景下,Maven是可以自动处理包冲突问题的,它遵循以下两个原则:
最短路径优先
如果有两个引用关系链A->B->C和A->D->E->C,那么Maven会自动选择短的这个引用链上的C的版本包
最先声明优先
如果两个引用关系链的长度是一样的。比如A->B ->C和A->D->C,那么Maven会自动选取先声明的B或者D对应的C的版本包
手动排除
如果Maven的自动策略无法处理冲突问题的话,那么可以通过手动排除的方法,在Pom文件中使用
<exclusion>
标签排除冲突的Jar包
1 | <dependency> |
版本锁定
把公共依赖提取出来,放到父版本的pom文件中,子项目一律使用继承的方式引用父项目的pom文件。
查看包冲突的有关工具
- Maven Helper
- mvn dependency:tree