Maven使用中常见的包冲突问题,以及Jar包管理

本文详细解释了Java开发中Maven管理包时遇到的包冲突问题,包括冲突定义、原因、传递依赖性规则,以及如何通过设置scope、optional、exclusions和dependencyManagement来管理和解决包冲突。
摘要由CSDN通过智能技术生成

对于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 通过设置第三方依赖不具备传递性

有以下两种做法

  1. 将该依赖的scope范围设置为非compile,比如<scope>provided</scope>
  2. 加上<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使用的一些总结和心得,内容浅显实用,如有不正确的内容请大家多多指正,促进我们在学习的道路上共同进步。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Scalzdp

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值