gradle依赖冲突处理办法

一、背景

在java项目中有maven,gradle这样的项目构建工具,可以通过gradle和maven的命令来快速对于项目进行编译,构建,以及项目所需要依赖的管理。

在实际工作中随着项目越来越大,引入依赖越来越多,依赖之间版本冲突无法避免,那么对于这种冲突我们应该怎么处理以确保项目使用正常的依赖版本呢。

思考:项目引入A,B两个模块,两个模块分别引入了abd.jar。A模块使用的1.0版本,B模块使用的2.0版本。那么最终项目构建完成,会使用1.0版本还是2.0版本呢?

二、默认管理办法

针对上面的问题,在不同的构建工具中使用了不同的管理策略。在gradle中选择使用高版本覆盖低版本。但是在maven中选择的是就近原则,也就是谁的依赖路径更短,就选择依赖路径相对较短的版本作为最终版版。

  • Gradle:更高版本优先
  • Maven:依赖路径更近优先

对于gradle的策略比较容易理解,对于maven的更短依赖路径应该怎么理解呢?我们通过下面的例子来看两者的不同。

现在有一个依赖包com.google.guava:guava,有不同的两个版本分别是 20.0 and 25.1-android

  • 项目直接依赖了 com.google.guava:guava:20.0
  • 项目也依赖了 com.google.inject:guice:4.2.2 ,guice包依赖 com.google.guava:guava:25.1-android

在这个场景下按照我们上面所说的:

  • gradle构建的项目最终会选择25.1-android版本,因为版本更高。
  • maven构建的项目会选择20.0,因为从依赖路径来看,20.0版本只有一级路径,25.1-android版本需要两级引入路径。

依赖查看命令

在maven和gradle中也提供了命令可以快速查看项目的引入依赖情况。

  • mvn dependency:tree
  • gradlew dependencies

对于gradle而言如果要单独看某一个jar的依赖可以执行dependencyInsight命令。例如:gradlew dependencyInsight --dependency srn-library单独查看srn-library包的依赖。

以下是gradle dependencies命令的示例输出。

shell 复制代码
./gradlew :gateway:dependencies
> Task :gateway:dependencies

------------------------------------------------------------
Project ':gateway'
------------------------------------------------------------

annotationProcessor - Annotation processors and their dependencies for source set 'main'.
--- org.projectlombok:lombok:1.18.4

apiElements - API elements for main. (n)
No dependencies

archives - Configuration for archive artifacts. (n)
No dependencies

bootArchives - Configuration for Spring Boot archive artifacts. (n)
No dependencies

compileClasspath - Compile classpath for source set 'main'.
+--- com.nimbusds:nimbus-jose-jwt:6.0.2
|    +--- com.github.stephenc.jcip:jcip-annotations:1.0-1
|    --- net.minidev:json-smart:[1.3.1,2.3] -> 2.4.8
|         --- net.minidev:accessors-smart:2.4.8
|              --- org.ow2.asm:asm:9.1
+--- org.projectlombok:lombok:1.18.4
+--- project :gateway:mbg
+--- com.anet.ops:base:1.5
|    +--- com.vip.vjtools:vjkit:1.0.0
|    |    +--- com.google.guava:guava:20.0 -> 23.0
|    |    |    +--- com.google.code.findbugs:jsr305:1.3.9 -> 3.0.2
|    |    |    +--- com.google.errorprone:error_prone_annotations:2.0.18 -> 2.10.0
|    |    |    +--- com.google.j2objc:j2objc-annotations:1.1
|    |    |    --- org.codehaus.mojo:animal-sniffer-annotations:1.14
|    |    +--- org.apache.commons:commons-lang3:3.7 -> 3.8.1
|    |    +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.36
|    |    +--- net.sf.dozer:dozer:5.5.1
|    |    |    +--- commons-beanutils:commons-beanutils:1.9.1
|    |    |    |    --- commons-collections:commons-collections:3.2.1
|    |    |    +--- org.apache.commons:commons-lang3:3.2.1 -> 3.8.1
|    |    |    +--- org.slf4j:slf4j-api:1.7.5 -> 1.7.36
|    |    |    --- org.slf4j:jcl-over-slf4j:1.7.5 -> 1.7.36
|    |    |         --- org.slf4j:slf4j-api:1.7.36
|    |    +--- junit:junit:4.12 -> 4.13.2
|    |    |    --- org.hamcrest:hamcrest-core:1.3 -> 2.2
|    |    |         --- org.hamcrest:hamcrest:2.2
|    |    +--- org.assertj:assertj-core:2.6.0 -> 3.22.0
|    |    --- org.mockito:mockito-core:1.10.19 -> 4.5.1
|    |         +--- net.bytebuddy:byte-buddy:1.12.9 -> 1.12.13
....

三、管理策略

在了解了默认依赖管理策略之后,我们知道了gradle会选择最新的版本保留依赖,可是并不是所有时候我们都希望这么做。有时候A,B两个模块有共同依赖jar abd之后,我们就是希望A模块中引入的依赖可以保留低版本,而B模块中的依赖保持高版本。

如果我们要特别设定某个模块使用特定的依赖,或者要求整个项目使用特定版本的依赖,我们可以使用force或者exclude等特性来处理。

Gradle中支持多种策略来自定义依赖解析,这里为大家描述几个日常使用的。基本上以下几个用法就能满足日常需求,如果想要了解更多可以阅读官方自定义依赖解析

exclude

可以通过exclude来指定特定的jar或者模块中,被exclude排除的依赖不引入到项目来,从而避免和当前项目中其他模块相同的依赖产生冲突。

shell 复制代码
dependencies {
    implementation('com.example:library:1.0.0') {
        // 排除名为 'unwanted-dependency' 的传递性依赖项
        exclude group: 'unwanted-group', module: 'unwanted-dependency'
    }
}

在这个示例中,implementation 声明中的 exclude 方法指定了要排除的传递性依赖项的组和模块名称。这样,Gradle 将会在解析依赖关系时会忽略指定的依赖项,从而确保它们不会被引入到项目中,避免和其他模块相同的依赖产生冲突。

force

如下代码所示,项目最终会强制使用17.5.0版本所有依赖版本,这样的调整范围是整个项目的,不管哪个依赖版本高低,项目最终都使用force指定的版本。

shell 复制代码
configurations.all {
   resolutionStrategy {
       force 'com.google.firebase:firebase-analytics:17.5.0'
   }
}

dependencyManagement

在maven和gradle中都可以使用dependencymanagement来统一指定版本。

shell 复制代码
<!-- 父模块的pom.xml -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8</version> <!-- 指定统一版本 -->
        </dependency>
    </dependencies>
</dependencyManagement>

四、特殊情况

使用gradle作为构建工具

如果依赖jar包是通过implementation引入项目的,那么在编辑阶段可能会出现低版本没有被覆盖的情况。直接在idea或者编辑器里面直接运行项目,可能会引发这种情况。

这是因为gradle对于implementation和api的差异导致的,通过implementation引入的依赖在编译阶段只会对当前模块生效不会传递给其他模块。

详情参考文章记录一次服务依赖处理冲突 - 掘金 (juejin.cn)

参考文档

相关推荐
救救孩子把4 分钟前
深入理解 Java 对象的内存布局
java
落落落sss7 分钟前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
万物皆字节12 分钟前
maven指定模块快速打包idea插件Quick Maven Package
java
夜雨翦春韭19 分钟前
【代码随想录Day30】贪心算法Part04
java·数据结构·算法·leetcode·贪心算法
我行我素,向往自由26 分钟前
速成java记录(上)
java·速成
一直学习永不止步31 分钟前
LeetCode题练习与总结:H 指数--274
java·数据结构·算法·leetcode·数组·排序·计数排序
邵泽明32 分钟前
面试知识储备-多线程
java·面试·职场和发展
Yvemil744 分钟前
MQ 架构设计原理与消息中间件详解(二)
开发语言·后端·ruby
程序员是干活的1 小时前
私家车开车回家过节会发生什么事情
java·开发语言·软件构建·1024程序员节
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript