什么是依赖冲突?
依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成了包版本冲突。
冲突会报如下错误:
网上有不同的说法,经个人测试下面的是正确的:
IDEA中安装Maven Helper
插件。
安装重启后,点击pom.xml可以看到两个选项卡,可以查看依赖的关系。
其中三个选项分别表示如下:
Conflicts
(查看所有冲突的依赖,所有的冲突依赖都会在下面显示,不冲突的不显示)All Dependencies as List
(列表形式查看所有依赖,冲突的依赖会红字显示)All Dependencies as Tree
(树形式查看所有依赖,冲突的依赖会红字显示)
注意
排查冲突的时候推荐使用第二种方式找到冲突项,搜索冲突项用第三种方式排除冲突。 从图中可以看出有哪些jar存在冲突,存在冲突的情况下最终采用了哪个依赖的版本。
标红的就是冲突版本,白色的是当前的解析版本
。 在解决冲突的时候直接把红色的排除是不对的,因为红色的本身就是冲突时被忽略的版本。 当我们排除依赖后直接点击Reimport
重新引用依赖,但是这时候页面可能不刷新,我们可以再次点击Reimport
,或者点击Refresh UI
。
对于部署环境中包含的jar可以使用provided
标识
如下所示
<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>
发生依赖冲突主要表现为系统启动或运行中会发生异常,99%表现为三种NoClassDefFoundError
、ClassNotFoundException
、NoSuchMethodError
。
Java 在装载一个目录下所有jar包时, 它加载的顺序完全取决于操作系统!其中Linux的顺序完全取决于INode的顺序。
定位方式
Maven Helper
插件解决冲突有两种方式
归纳了解了几种业内的解决方案如下,各有优劣
查看上面的冲突检测进行升降版本
当然不是所有情况都可以通过升降级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。
比如我这Mysql中依赖的版本
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.19.4</version>
</dependency>
其它依赖的版本
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
两者互相冲突但是都需要。
新建项目
<?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>
打包
mvn clean deploy
这里发布到本地Maven中,方便引用。
发布失败可以先卸载之前的
mvn dependency:purge-local-repository -DmanualInclude="cn.psvmc:mysql8"
原来的Jar
<!--操作Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
可以替换为
<!--操作Mysql-->
<dependency>
<groupId>cn.psvmc</groupId>
<artifactId>mysql8</artifactId>
<version>1.1</version>
</dependency>
原项目导出一下验证一下
mvn dependency:copy-dependencies -DincludeScope=compile
一般我们在解决依赖冲突的时候,都会选择保留jar高的版本,因为大部分jar在升级的时候都会做到向下兼容,所以只要保留高的版本就不会有什么问题。
但是有些包,版本变化大没法去做向下兼容,高版本删了低版本的某些类或者某些方法,那么这个时候就不能选择高版本,但也不能选择低版本。这种只能使用Shade处理。