Maven依赖原则及如何解决Maven依赖冲突

news2024/12/24 21:22:48

前言

在大数据应用中,现在发现依赖关系非常复杂,在上线之前很长测试,前一段时间在部署udf 出现了导致生产Hiveserver2
宕机问题,出现严重事故。现在就咨询研究一下。Maven虽然已经诞生多年,但仍然是当前最流行的Java系项目管理工具之一。在使用Maven的过程中,比较常见也相对难解决的问题就是依赖冲突,Gradle和sbt也同样存在此问题。本文先简述Maven中的依赖原则,然后通过两个例子讲述解决方法。

Maven依赖原则

Maven具有传递依赖(transitive dependency)的特性,即如果组件A依赖组件B,组件B依赖组件C,那么A就会自动产生对C的依赖,以此类推。这使我们不必再关心众多依赖背后的关系,只需要着眼于自己需要的组件,很方便。

但是,传递依赖会使得项目依赖空间逐渐扩展。当依赖非常多时,不可避免地会出现相同组件版本不同的情况。Maven内置了依赖仲裁(dependency mediation)机制来处理该问题,具体来说有以下两条。

最近依赖优先(nearest definition wins)

如果有如下的Maven依赖关系树:
在这里插入图片描述

在这种情况下,根组件R传递依赖了两个版本不同的X。由于R→C→X这条路径比R→A→B→X要短,也就是说X 1.0比X 2.0距离R更近,所以会选择X 1.0作为依赖,X 2.0会被忽略。

最先声明优先(first declaration wins)

如果出现了下图这种情况:
在这里插入图片描述

同一组件的不同版本深度相同的话,就会按声明的顺序判定。图中R的依赖声明顺序是A→B→C,所以会选择A所引用的X 1.0作为依赖。如果反过来的话,就会选择X 3.0。

Maven的依赖仲裁已经可以摆平大部分问题,但是有时由于组件兼容性不好,就需要自己解决冲突了。下面举两个来自我们日常的简化版例子。

例子一:应用Dependency插件和依赖原则
有一个与推荐系统相关的项目需要同时访问HBase集群和Codis集群,pom文件中有如下依赖(实际上我们对Codis和Jodis源码做了大量改动,但为了方便举例,这里用原版):

<properties>
  <hbase.version>1.2.0</hbase.version>
  <jodis.version>0.5.1</jodis.version>
</properties>
 
<dependencies>
  <dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>${hbase.version}</version>
  </dependency>
 
  <dependency>
    <groupId>io.codis.jodis</groupId>
    <artifactId>jodis</artifactId>
    <version>${jodis.version}</version>
  </dependency>
</dependencies>

然后普通地创建一个Codis连接池:

JedisResourcePool jedisResourcePool = RoundRobinJedisPool.create()
    .curatorClient("10.10.99.130:2181,10.10.99.132:2181,10.10.99.133:2181,10.10.99.124:2181,10.10.99.125:2181", 10000)
    .zkProxyDir("/jodis/bd-redis")
    .build();

结果抛出找不到ZK构造方法的异常:
在这里插入图片描述

看起来好好的Maven工程抛出NoClassDefFoundError或者NoSuchMethodError,首先要考虑的就是Maven依赖冲突。我们用Maven Dependency插件来检查依赖,在工程目录下执行mvn dependency:tree命令打印依赖树,并加上-Dverbose参数输出详细的冲突信息,加上-Dincludes:org.apache.zookeeper过滤出与ZK相关的依赖,输出如下:
在这里插入图片描述

据上述依赖原则,生效的依赖是3.4.6版本的,但是Jodis中依赖的是较高版本3.4.11(被忽略了),其调用的ZK构造方法在低版本中并不存在,故抛出NoSuchMethodError。问题找到了,解决方法也有多种:

  1. 将两个依赖调换一下位置,让Jodis依赖的高版本ZK先声明。
  2. 在原pom中依赖列表的第一位添加ZK 3.4.11,这样它的路径就是最短的了。
  3. 将ZK 3.4.11写在标签下,它会强制让项目中所有ZK依赖都应用该版本。
  4. 排除掉hbase-client里的低版本依赖,即:
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>${hbase.version}</version>
    <exclusions>
      <exclusion>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
      </exclusion>
    </exclusions>
  </dependency>

另外,也可以借助图形化工具来检查依赖,比如IntelliJ IDEA里的Maven Helper等,不再赘述。

下面来看另一种情况。

例子二:应用Shade插件
上述项目中会通过HBase连接做查询,简单描述如下:

Configuration conf = HBaseConfiguration.create();
conf.set("...", "...");
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(TableName.valueOf("table"));
Get get = new Get(Bytes.toBytes("row_key"));
Result result = table.get(get);

该项目后来又需要用到Guava 20.0。添加依赖后,抛出如下异常:
在这里插入图片描述

Stopwatch是Guava中的基础类,HBase的元数据表定位器MetaTableLocator中用到了它。用Dependency插件观察Guava相关的依赖:
在这里插入图片描述

这个依赖树复杂些,出现了4个Guava版本,从低到高分别是11.0.2(Hadoop)、12.0.1(HBase)、20.0(实际应用的版本)和23.0(Jodis)。根据先验知识,Guava从16.0版本开始发生了很大变化,没有向前兼容,所以HBase的API会出现问题。由于我们必须使用高版本Guava,故不能通过例子一中的任何方法来解决冲突,这时就可以让Maven Shade插件上场了。

简单地讲,Shade插件负责将Maven项目及其依赖打成Uber JAR包,并且还能将依赖通过重命名等方式隐藏起来。我们就可以将高版本Guava(低版本也行,总之是冲突方之一)的依赖重命名,从而消除冲突。从上图中可以发现,Jodis已经引用了更高版本的Guava 23.0,我们可以直接Shade它,因为20.0和23.0是兼容的。

打开Jodis的pom文件,添加以下内容:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>2.4.3</version>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <relocations>
          <relocation>
            <pattern>com.google.guava</pattern>
            <shadedPattern>shaded.com.google.guava</shadedPattern>
          </relocation>
          <relocation>
            <pattern>com.google.common</pattern>
            <shadedPattern>shaded.com.google.common</shadedPattern>
          </relocation>
        </relocations>
      </configuration>
    </execution>
  </executions>
</plugin>

重新构建之后,删去项目中原来的Guava引用,就可以直接导入Shade过的高版本Guava的类,冲突解除:

import shaded.com.google.common.hash.Funnels;
import shaded.com.google.common.hash.Hashing;

同理,如果不Shade Jodis,而去Shade HBase相关依赖的话,可以不必去修改HBase的源码。另外创建一个新的空Maven项目,在其pom文件中写入HBase相关的依赖,再打包即可。当实际引用时,就要在依赖里写自定义Shade包的GAV,而不是原依赖的GAV了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/499975.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Arrays:点燃你的数组操作技巧的隐秘武器。

前言 数组在 Java 中是一种常用的数据结构&#xff0c;用于存储和操作大量数据。但是在处理数组中的数据&#xff0c;可能会变得复杂和繁琐。Arrays 是我们在处理数组时的一把利器。它提供了丰富的方法和功能&#xff0c;使得数组操作变得更加简单、高效和可靠。无论是排序、搜…

【c语言】字符串类型转换 | itoa函数的使用

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

MySQL innodb介绍

InnoDB引擎的优点是支持兼容ACID的事务&#xff0c;以及参数完整性&#xff08;即对外键的支持&#xff09;。 Oracle公司2005年10月收购了Innovase&#xff1b;Innobase采用双认证授权。它使用GNU发行&#xff0c;也允许其他想将InnoDB结合到商业软件的团体好的授权 mysql5.…

Java 动态原理详解

Java 动态代理是一种非常重要的编程技术&#xff0c;它在很多场景下都有着广泛的应用。本文将介绍 Java 动态代理的实现原理&#xff0c;并附上相应的源码&#xff0c;以帮助读者更好地理解和应用这一技术。 一、什么是 Java 动态代理&#xff1f; Java 动态代理是一种在运行时…

【并发基础】Happens-Before模型详解

目录 一、Happens-Before模型简介 二、组成Happens-Before模型的八种规则 2.1 程序顺序规则&#xff08;as-if-serial语义&#xff09; 2.2 传递性规则 2.3 volatile变量规则 2.4 监视器锁规则 2.5 start规则 2.6 Join规则 一、Happens-Before模型简介 除了显示引用vo…

双目测距--5 双目相机 联合 YOLOv8

目录 效果&#xff1a; 1、立体矫正不改变图像尺寸 2、视差图尺寸与原图尺寸一致 3、视差图、深度信息图 4、几个重要的函数 createTracker() 5、代码 main.cpp utils.cpp 效果&#xff1a; 1、立体矫正不改变图像尺寸 左右相机图像立体矫正后&#xff0c;图像尺寸为变化…

freeRTOS中使用看门狗的一点思考

关于看门狗想必各位嵌入式软件开发的朋友应该都不会陌生的。在嵌入式软件开发中&#xff0c;看门狗常被用于监测cpu的程序是否正常在运行&#xff0c;如果cpu程序运行异常会由看门狗在达到设定的阈值时触发复位&#xff0c;从而让整个cpu复位重新开始运行。 看门狗的本质是一个…

Qt QQueue 安全的多线程队列、阻塞队列

文章目录 1. C queue 队列基本用法2. Qt QQueue 队列基本用法3. Qt QQueue 多线程队列4. Qt BlockingQueue 自定义线程安全的阻塞队列 1. C queue 队列基本用法 在C中&#xff0c;queue是一个模板类&#xff0c;用于实现队列数据结构&#xff0c;遵循先进先出的原则。 ♦ 常用…

测试3:用例

目录 1.测试用例的基本要素 2.测试用例的设计方法 1.基于需求的设计方法 2.等价类 1.概念 2.步骤: 3.例子 3.边界值 1.概念 2.步骤 3.例子 4.判定表 1.概念 2.设计测试用例 3.例子 5.正交排列 1.什么是正交表 2.测试用例 3.如何通过正交表设计测试用例 6.场景…

(3)Qt——信号槽

目录 1.信号槽的概念** 2.信号槽的连接*** 2.1自带信号 → 自带槽 2.2 自带信号 → 自定义槽 2.3 自定义信号 3. 参数传递** 3.1 全局变量 3.2 信号槽传参 4. 对应关系** 4.1 一对多 4.2 多对一 1.信号槽的概念** 信号槽指的是信号函数与槽函数的连接&#xff0c;可…

AI绘图入门 安装 stable-diffusion-webui

下面介绍了N卡&#xff0c;A卡&#xff0c;或CPU跑 stable-diffusion-webui的方法。 1.安装python 3.10.x https://www.python.org/downloads/ 2.安装Git https://git-scm.com/downloads 【非必要】打开代理工具&#xff08;比如clash&#xff09;然后在cmd配置git的http和…

软件测试相关概念

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 需求需求的定义测试人员眼中的需求为什么需求对测试人员如此重要如何深入理解需求 测试用例定义为什么要有测试用例 软件错误…

IT服务规划设计笔记

规划设计处于整个IT服务生命周期中的前端&#xff0c;其主要目的在于&#xff1a; &#xff08;1&#xff09;设计满足业务需求的IT服务 &#xff08;2&#xff09;设计SLA、测量方法和指标 &#xff08;3&#xff09;设计服务过程及其控制方法 &#xff08;4&#xff09;规…

learn_C_deep_9 (汇编角度理解return的含义、const 的各种应用场景)

return 关键字 不知道我们大家是否有一个疑惑&#xff1a;我们下载一个大型游戏软件&#xff08;王者荣耀&#xff09;&#xff0c;都要花几个小时去下载&#xff0c;但是一旦我们游戏连输&#xff0c;想要删除这个软件的时候&#xff0c;它仅仅只需要十几秒&#xff0c;这是为…

主题建模和文本聚类:理论与实践

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

真题详解(3FN)-软件设计(六十九)

真题详解&#xff08;构造二叉树&#xff09;-软件设计&#xff08;六十八)https://blog.csdn.net/ke1ying/article/details/130536155 学生信息学生id姓名性别1{家长ID}*班主任班级。 解析&#xff1a;当存在1对多的情况&#xff0c;要写个1{}*&#xff0c;中间用{}。 ()表…

一篇学会Gitlab搭建及使用

目录 一、Gitlab介绍 1、什么是Gitlab 二、搭建gitlab并实现ssl 1、配置yum源或下载gitlab包 2、安装依赖软件及获取GPG密钥 3、安装gitlab-ce 4、创建私有密钥 5、创建私有证书 6、创建CRT签名证书 7、利用openssl签署pem 证书 8、配置证书到gitlab 9、初始化gitla…

读书笔记:《图解CIO工作指南》

《图解CIO工作指南》第 4 版&#xff0c;日 . 野村综合研究所系统咨询事业本部 著&#xff0c;周自恒 译 大数据、云计算时代下的IT战略和IT实务 CIO工作&#xff1a;IT管理、IT架构、IT实践 以着眼企业未来的观点进行构思&#xff1a;可视化&#xff08;业务与系统&am…

初始化vue中data中的数据

当组件的根元素使用了v-if的时候, 并不会初始化data中的数据 如果想完全销毁该组件并且初始化数据,需要在使用该组件的本身添加v-if 或者是手动初始化该组件中的数据 初始化化数据的一些方法 Object.assign(this.$data, this.$options.data()) this.$data&#xff1a;当前的da…

TortoiseGit(大乌龟)安装教程(Git 图形化工具,告别手敲命令)

TortoiseGit安装教程 1. 下载TortoiseGit 官方下载地址&#xff1a;https://tortoisegit.org/download/ 自行选择下载对应版本&#xff08;大部分位64位&#xff09;&#xff0c;进行下载 2. 安装TortoiseGit 打开安装包&#xff0c;如下图所示&#xff1a; 点击 Next&…