首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Maven依赖Jar冲突排查及解决

Maven依赖Jar冲突排查及解决

作者头像
码客说
发布2023-10-09 08:27:02
发布2023-10-09 08:27:02
2.3K00
代码可运行
举报
文章被收录于专栏:码客码客
运行总次数:0
代码可运行

前言

什么是依赖冲突?

依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成了包版本冲突。

冲突会报如下错误:

  • Caused by:java.lang.NoSuchMethodError
  • Caused by: java.lang.ClassNotFoundException

依赖生效原则

网上有不同的说法,经个人测试下面的是正确的:

  • 最短路径原则: 面对多级(两级及以上)的不同依赖,会优先选择路径最短的依赖;
  • 声明优先原则: 面对多级(两级及以上)的同级依赖,先声明的依赖会覆盖后声明的依赖;
  • 一级依赖中,后声明的依赖会覆盖先声明的依赖,并且如果是前面的版本低后面的版本高会显示冲突,反之却不会显示冲突;

解决冲突的方式

  1. 根据优先原则,把需要的版本放在路径最短的位置或最先声明
  2. 排除其他版本的依赖
  3. 使用包名替换(Shade)

冲突检测插件

IDEA中安装Maven Helper插件。

安装重启后,点击pom.xml可以看到两个选项卡,可以查看依赖的关系。

其中三个选项分别表示如下:

  1. Conflicts(查看所有冲突的依赖,所有的冲突依赖都会在下面显示,不冲突的不显示)
  2. All Dependencies as List(列表形式查看所有依赖,冲突的依赖会红字显示)
  3. All Dependencies as Tree(树形式查看所有依赖,冲突的依赖会红字显示) 注意

排查冲突的时候推荐使用第二种方式找到冲突项,搜索冲突项用第三种方式排除冲突。 从图中可以看出有哪些jar存在冲突,存在冲突的情况下最终采用了哪个依赖的版本。 标红的就是冲突版本,白色的是当前的解析版本。 在解决冲突的时候直接把红色的排除是不对的,因为红色的本身就是冲突时被忽略的版本。 当我们排除依赖后直接点击Reimport重新引用依赖,但是这时候页面可能不刷新,我们可以再次点击Reimport,或者点击Refresh UI

对于部署环境中包含的jar可以使用provided标识

如下所示

代码语言:javascript
代码运行次数:0
运行
复制
<dependency>
  <groupId>org.apache.hive</groupId>
  <artifactId>hive-exec</artifactId>
  <version>${hive.version}</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-table-planner_${scala.binary.version}</artifactId>
  <version>${flink.version}</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-table-planner-blink_2.12</artifactId>
  <version>${flink.version}</version>
  <scope>provided</scope>
</dependency>

Jar包冲突的解决方法

发生依赖冲突主要表现为系统启动或运行中会发生异常,99%表现为三种NoClassDefFoundErrorClassNotFoundExceptionNoSuchMethodError

Java 在装载一个目录下所有jar包时, 它加载的顺序完全取决于操作系统!其中Linux的顺序完全取决于INode的顺序。

定位方式

  1. 在IDEA中(快捷键Ctrl+N)查找异常栈中提示缺失的类在哪些版本的jar包中有。
  2. Maven Helper插件

解决冲突有两种方式

  • 检测冲突的插件升降版本解决
  • Jar包隔离
  • 包名替换

归纳了解了几种业内的解决方案如下,各有优劣

  1. spring boot方式,统一管理各个组件版本,简洁高效,但遇到必须使用不同版本jar包时,就不行了
  2. sofa-ark 用FatJar技术去实现OSGI的功能,jar包隔离原理上跟osgi一致,不过基于fat jar技术,通过maven 插件来简化复杂度,比较轻量,也支持服务热部署热更新等功能。
  3. shade 也有maven插件,通过更改jar包的字节码来避免jai包冲突,jar包冲突的本质是类的全限定名(包名+类名)冲突了,通过全限定名不能定位到你想用的那个类,maven-shade插件可以更改jar包里的包名,来达到解决冲突的目的。
  4. 自己定义classload,反射调用冲突方法,代码量太大,不通用,但是会帮助理解上面组件的原理。

升降依赖版本解决

查看上面的冲突检测进行升降版本

Jar隔离

当然不是所有情况都可以通过升降级jar解决冲突,举个例子:

如上图假设应用系统同时依赖A.jar,B.jar,而A.jar,B.jar都依赖protobuf-java,系统运行时都会分别用到A.jar,B.jar中protobuf部分的功能,而且A.jar,B.jar依赖的protobuf版本无法通过升高降低版本调整到一致。由于protobuf-java3.0版本序列化协议,类内容各方面都不兼容protobuf-java2.0版本。

这种情况无论如何调整依赖都无法解决冲突的问题

sofa-ark

sofa-ark 框架支持单独application 和 sofaboot 两种方式,满足单独使用和web框架下的jar包隔离,还能基于zk 完成服务热部署等高大上的功能,但是配置方式略复杂。

很不幸我的应用是跑在flink里的,做不到将容器启动函数放在main的第一句,因为本来就在flink的容器里了,所以此种方案pass。

包名替换(shade)

比如我这Mysql中依赖的版本

代码语言:javascript
代码运行次数:0
运行
复制
<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java</artifactId>
  <version>3.19.4</version>
</dependency>

其它依赖的版本

代码语言:javascript
代码运行次数:0
运行
复制
<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java</artifactId>
  <version>2.5.0</version>
</dependency>

两者互相冲突但是都需要。

新建项目

代码语言:javascript
代码运行次数:0
运行
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <groupId>cn.psvmc</groupId>
  <artifactId>mysql8</artifactId>
  <version>1.1</version>
  <modelVersion>4.0.0</modelVersion>
  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>
  <repositories>
    <repository>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <relocations>
                <relocation>
                  <pattern>com.google.protobuf</pattern>
                  <shadedPattern>cn.psvmc.protobufv3</shadedPattern>
                </relocation>
              </relocations>
              <filters>
                <filter>
                  <artifact>*:*</artifact>
                  <excludes>
                    <exclude>META-INF/maven/**</exclude>
                  </excludes>
                </filter>
              </filters>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

打包

代码语言:javascript
代码运行次数:0
运行
复制
mvn clean deploy

这里发布到本地Maven中,方便引用。

发布失败可以先卸载之前的

代码语言:javascript
代码运行次数:0
运行
复制
mvn dependency:purge-local-repository -DmanualInclude="cn.psvmc:mysql8"

原来的Jar

代码语言:javascript
代码运行次数:0
运行
复制
<!--操作Mysql-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.30</version>
</dependency>

可以替换为

代码语言:javascript
代码运行次数:0
运行
复制
<!--操作Mysql-->
<dependency>
  <groupId>cn.psvmc</groupId>
  <artifactId>mysql8</artifactId>
  <version>1.1</version>
</dependency>

原项目导出一下验证一下

代码语言:javascript
代码运行次数:0
运行
复制
mvn dependency:copy-dependencies -DincludeScope=compile

总结

一般我们在解决依赖冲突的时候,都会选择保留jar高的版本,因为大部分jar在升级的时候都会做到向下兼容,所以只要保留高的版本就不会有什么问题。

但是有些包,版本变化大没法去做向下兼容,高版本删了低版本的某些类或者某些方法,那么这个时候就不能选择高版本,但也不能选择低版本。这种只能使用Shade处理。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 依赖生效原则
  • 解决冲突的方式
  • 冲突检测插件
  • Jar包冲突的解决方法
    • 升降依赖版本解决
    • Jar隔离
    • 包名替换(shade)
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档