对于java开发人员,我们通常会使用Maven来管理项目中的应用的第三方包,随着Java在生产系统中被广泛的应用,java生态圈得到了极大的发展,java开发的应用越来越复杂,第三方包的使用也就越来越多,Maven管理着的冲突变成了非常严重的问题。在Maven发生了包冲突不像自己编写的代码,其问题通常隐藏比较深,发现并排除包冲突不是一件容易解决的事情,为了较深入了解maven管理的包,我们首先需要对包冲突这个问题进行认识,以及Maven管理包方式进行认识。
一、包冲突问题
1. 什么是包冲突?
JAR包冲突是指在一个项目中引入了两个或多个不同版本的JAR包,而这些JAR包中都包含有相同名称的类、接口、资源等,从而引起冲突。这种冲突通常发生在项目构建和运行时,导致程序无法正确地找到所需的类或资源,从而报错。通常会抛出一个ClassNotFoundException这样的异常。
2. 包冲突的原因
导致包冲突主要有以下三种情况
- 依赖项的版本不一致
- 传递性依赖导致的冲突
- 不同项目之间的依赖冲突
3.传递依赖性
A依赖B,B依赖C.A能使用C中的类或代码吗?
是可以的
maven中包的依赖有几条原则可以参考
a.路径最短者优先.(–>代表依赖)
举例:A–>B–>C
B–>x.jar(1.0版本)
C–>x.jar(1.1版本)
则最后A中编译的使用的是B中的1.0版本.
b.相同路径先声明者优先.
举例:A–>B A–>C
B–>x.jar(1.0版本)
C–>x.jar(1.1版本)
此时两个依赖路径是相同的,谁先声明(代码放在前面),A就使用谁.
二、如何进行包冲突的解决
一些情况下,一个java的项目,引入了大量的第三方包,很难避免会出现第三方包不冲突的情况,大部分情况只要冲突不影响系统正常运行,可以允许冲突存在,我们需要解决的是那种会导致项目无法正常运行的包冲突。下面是总结的一些方法可以较好的管理jar版本和解决jar包冲突的方式。
2.1 通过设置第三方依赖不具备传递性
有以下两种做法
- 将该依赖的scope范围设置为非compile,比如<scope>provided</scope>
- 加上<optional>true</optional>
2.2 scope
maven常用scope指定maven依赖的作用域:compile、runtime、privided、test、system
compile:scope的默认值,编译期、运行期有效,常见的jar比如:spring-core。会打包到项目包中
runtime:运行期有效,编译期无效(jar中的类无法显示的在程序中引用,否则编译不通过),常见的jar比如:mysql-connector-java。会打包到项目中。
provided:编译期、运行期有效,常见的jar比如:servlet-api。不会打包到项目中。
test:jar中的类,只针对测试模块,不能用在main程序中,常见的jar比如:junit。不会打包到项目中。
system:一般配合systemPath节点使用。不会打包到项目中
例如我们在maven中指定第三方包依赖限制<scope>provided</scope>,表明有两层含义:
只在编译期提供,不在运行期提供
依赖不具有传递性
比如:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
因为项目中会用到servlet-api包,但是servlet-api依赖的第三方包,通常项目不会使用,只会在IDE编译期有效,保证项目能够正常编译构建,而正式包就不会将其依赖的第三方包打包到项目中。
2.3 optional
optional表示是否会传递依赖,有两个可填值(假如不声明optional标签,默认就是false):
false: 传递依赖
true:不传递依赖
举例:A引用了B的依赖,而B又引用了C依赖。
假如B引用C依赖的时候没有设置optional,那么A是可以使用C依赖的。
假如B引用C依赖的时候将optional标签设置为了true,那么在A当中就无法使用C依赖相关的方法,并且A调用B依赖的方法,而B依赖方法使用到了C,这时候会报找不到C依赖下的类,因为C不参与A的打包。
就比如:<optional>true</optional>只有一层含义:
依赖不具有传递性,但是只能使用依赖范围是compile的.也就是说非compile范围的依赖不能传递.
所以当只想当前依赖不被传递的时候,一般会选择<optional>true</optional>
2. 4使用 Maven 的 <dependencyManagement>
标签
那么它的作用究竟是什么呢?我们直接把标签名翻译过来,其意思为“依赖管理”,是的,它在 Maven 中提供了一种管理依赖版本号的方式。
dependencyManagement中定义的只是依赖的声明,并不实现引入,因此一般只用于依赖管理.
在常规使用中,一个 Maven 项目如果要引用某个依赖,那么直接就在 dependencies 中添加 dependency 描述所需的依赖坐标信息即可完成。这样就达到了一个要什么,就直接写什么的效果,决定权都在是否用 dependency 指定了引用构件的坐标。
但在实际项目管理过程中,会有多个模块,如果把这些模块所需的依赖各自引入,不仅会导致管理的不方便,更甚会有版本冲突等问题,所以此时应该设计一个全局的依赖管理。也就是说,把整个项目要引用的依赖,事先分析整理好,形成一个全局的集合。当某个 Maven 模块需要具体引用某依赖的时候,直接在集合中指定若干个。这样就可以实现整个项目依赖的全局管理,不至于零碎地分布在每个 Maven 模块中。
正是基于这样的考虑,就产生 dependencyManagement 的设计,在此标签元素中声明所需依赖的版本号等信息,那么所有子项目再次引入此依赖 jar 包时则无需显式的列出版本号。Maven 会沿着父子层级向上寻找拥有 dependencyManagement 元素的项目,然后使用它指定的版本号。
例如:
<properties>
<springframework.version>1.2.3.RELEASE</springframework.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springframework.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
此配置即声明了 Spring Boot 的版本信息,注意其中还有 type(打包类型) 和 scope 标签,import 表示当前项目的依赖可用于另外一个项目,并且 import 范围只有在 denpendencyManagement 元素下才有效果,由于其范围有特殊性,一般都是指向打包类型为 pom 的模块。
如果某子项目中需要使用上述的依赖,直接引入即可,并且不必再指定版本号,它会自动继承父类的版本信息。子项目的 pom.xml 如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
当我们引入spring-boot-starter-web的1.2.3.RELEASE版本的时候,我们默认就将1.2.3.RELEASE所依赖的第三方包都引入了
2.5 使用 <exclusions>
标签排除冲突的依赖项
A依赖B,B依赖C,在maven当中如果A依赖B,那么A当中就间接的依赖的C,如果要求A不需要依赖C,这个时候该标签的作用就使用到了。
这个exclusions标签主要是排除引入的包所依赖的特定包,比如:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
默认的spring-boot-starter-web是依赖了spring-boot-starter-tomcat这个包的,但是某些时候,我们项目中需要排除spring-boot-starter-tomcat,但是又要依赖spring-boot-starter-web,我们就可以使用如上方式,将spring-boot-starter-tomcat进行排除
2.6 手动解决冲突
如果我们在事先就知道一些存在的包冲突我们就按部就班通过maven进行冲突避免,但是不可避免会出现包冲突,此时我们需要用到包冲突解决神器,Maven的Dependency Analyzer
通过这个可视化工具可以很方便的查看冲突的包,然后根据我们对项目的使用方法的分析,选择出现了冲突的版本号,右键Exclude,进行排除,他会在maven中通过2.5节的<exclusions>标签将对应的版本的包进行排除
三、总结
通过以上内容,我们先了解了什么是包冲突及其发生原因,我们了解了包依赖的一些特性,并且Maven提供了多种方式对包进行有效的管理,这是我对Maven使用的一些总结和心得,内容浅显实用,如有不正确的内容请大家多多指正,促进我们在学习的道路上共同进步。