【gradle冲突解决1】 降级版本和排除依赖项 &&升级传递依赖项的版本

【gradle冲突解决1】 降级版本和排除依赖项 &&升级传递依赖项的版本

降级版本和排除依赖项

覆盖传递依赖项的版本

Gradle通过选择依赖图中找到的最新版本来解决任何依赖版本冲突。有些项目可能需要偏离默认行为,并强制执行较早版本的依赖关系,例如,如果项目的源代码依赖于比某些外部库更旧的依赖关系的较旧API。

强制使用依赖关系的版本需要一个有意识的决定。如果外部库在没有这些依赖项的情况下无法正常工作,更改传递依赖关系的版本可能会导致运行时错误。考虑升级您的源代码以使用较新版本的库作为替代方法。

通常,强制依赖关系是用来降级依赖关系的。

降级的不同用例情形可能有:

  • 最新版本中发现了错误
  • 您的代码依赖于低版本,该版本与高版本不兼容
  • 您的代码不依赖于需要更高版本依赖项的代码路径

在所有情况下,最好是明确声明您的代码严格依赖于传递依赖项的某个版本。使用严格的版本,您将实际上依赖于您声明的版本,即使传递依赖项表示不同。

严格依赖项在某种程度上类似于Maven的最近优先策略,但存在细微差异:

  • 严格依赖关系不会遇到排序问题:它们以子图的传递方式应用,并且依赖项的声明顺序无关紧要。
  • 冲突的严格依赖关系将触发构建失败,您必须解决该冲突。
  • 严格依赖关系可以与丰富的版本一起使用,这意味着最好是使用严格范围和单个首选版本来表达要求。

假设一个项目使用HttpClient库进行HTTP调用。HttpClient作为传递依赖项引入了Commons Codec,并且使用版本1.10。然而,项目的生产源代码需要Commons Codec 1.9中的API,该API在1.10中不再提供。可以通过在构建脚本中将其声明为严格版本来强制执行依赖版本:

// Example 1. Setting a strict version
dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.4'
    implementation('commons-codec:commons-codec') {
        version {
            strictly '1.9'
        }
    }
}

使用严格版本的后果

使用严格版本必须经过仔细考虑,特别是对于库作者。作为生产者,严格版本将实际上像是一个强制:版本声明优先于传递依赖图中找到的任何其他版本。特别地,严格版本将覆盖同一模块上在子图中找到的任何其他严格版本。

然而,对于消费者而言,在图解析期间仍然将全局地考虑严格版本,并且如果消费者不同意,则可能会触发错误。

例如,假设您的项目B严格依赖于C:1.0。现在,一个消费者A同时依赖于B和C:1.1。

那么这将触发解析错误,因为A表示它需要C:1.1,但在其子图中,B严格需要1.0。这意味着如果您选择了严格约束中的单个版本,那么该版本将无法升级,除非消费者还在同一模块上设置严格版本约束。

在上面的示例中,A必须声明它严格依赖于1.1。

因此,一个好的做法是,如果使用严格版本,应以范围和首选版本的形式来表达它们。例如,B可以说,而不是严格1.0,它严格依赖于[1.0,2.0[范围,并首选1.0。然后,如果消费者选择1.1(或范围内的任何其他版本),构建将不再失败(解决约束)。

强制依赖关系与严格依赖关系

如果项目在配置级别需要依赖项的特定版本,可以通过调用ResolutionStrategy.force(java.lang.Object[])方法来实现。

// Example 2. Enforcing a dependency version on the configuration-level
configurations {
    compileClasspath {
        resolutionStrategy.force 'commons-codec:commons-codec:1.9'
    }
}

dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.4'
}

排除传递依赖项

前面的部分展示了如何强制执行传递依赖项的特定版本,

而本节则介绍了通过排除完全移除传递依赖项的方法。

与强制使用依赖关系一样,完全排除依赖关系需要一个有意识的决定。完全排除传递依赖关系可能会导致运行时错误,如果外部库没有正确地功能。如果使用排除,确保您不使用任何需要已排除依赖项的代码路径,并进行充分的测试覆盖。

可以在声明的依赖项级别上排除传递依赖项。排除通过键/值对的形式拼写,通过属性组和/或模块来指定,如下例所示。有关更多信息,请参阅ModuleDependency.exclude(java.util.Map)。

// Example 3. Excluding a transitive dependency for a particular dependency declaration
dependencies {
    implementation('commons-beanutils:commons-beanutils:1.9.4') {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
}

在此示例中,我们添加了对commons-beanutils的依赖关系,但排除了传递依赖项commons-collections。在我们的代码中,我们只使用beanutils库中的一个方法PropertyUtils.setSimpleProperty()。使用这种方法来修改现有setter不需要来自commons-collections的任何功能,我们通过测试覆盖验证了这一点。

事实上,我们表达的是我们仅使用库的子集,而不需要commons-collection库。这可以被视为隐式定义了一个未被commons-beanutils明确声明的特性变体。然而,通过这样做,未经测试的代码路径断裂的风险增加了。

例如,在上面的代码中,我们使用setSimpleProperty()方法来修改Person类中由setter定义的属性,这工作正常。如果我们尝试设置在类上不存在的属性,我们应该得到错误,例如Unknown property on class Person。然而,因为错误处理路径使用了来自commons-collections的类,所以现在我们得到的错误是NoClassDefFoundError: org/apache/commons/collections/FastHashMap。因此,如果我们的代码更加动态,并且我们忘记充分覆盖错误情况,我们的库的使用者可能会遇到意外错误。

这只是一个示例,用于说明潜在的陷阱。实际上,更大型的库或框架可以引入大量的依赖项。如果这些库无法单独声明功能,并且只能以“全部还是无”方式使用,则排除可以是减少实际所需功能集有效方法

好处是,与Maven相比,Gradle的排除处理考虑了整个依赖图。因此,如果有多个对库的依赖关系,仅当所有依赖关系都同意排除时才会应用排除。例如,如果我们将opencsv作为项目的另一个依赖项添加到上面的示例中,并且opencsv本身也依赖于commons-beanutils,则不再排除commons-collection,因为opencsv本身没有排除它。

// Example 5. Excludes only apply if all dependency declarations agree on an exclude
dependencies {
    implementation('commons-beanutils:commons-beanutils:1.9.4') {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
    implementation 'com.opencsv:opencsv:4.6' // depends on 'commons-beanutils' without exclude and brings back 'commons-collections'
}

如果我们仍然希望排除commons-collections,因为我们对commons-beanutils和opencsv的组合使用不需要它,我们需要从opencsv的传递依赖项中也排除它。

// Example 6. Excluding a transitive dependency for multiple dependency declaration
dependencies {
    implementation('commons-beanutils:commons-beanutils:1.9.4') {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
    implementation('com.opencsv:opencsv:4.6') {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
}

历史上,排除还用于解决某些依赖关系管理系统不支持的其他问题。然而,Gradle提供了各种功能,可能更适合解决特定的用例。您可以考虑查看以下功能:

  • 更新或降级依赖项版本:如果依赖项的版本冲突,通常最好通过依赖约束来调整版本,而不是尝试排除具有不需要的版本的依赖项。
  • 组件元数据规则:如果库的元数据明显错误,例如,如果它包含在编译时不需要的依赖项,则可以在组件元数据规则中删除该依赖项。通过这样做,您告诉Gradle两个模块之间的依赖关系从不需要-即元数据错误-因此不应考虑。如果您正在开发库,则必须意识到此信息不会发布,因此有时排除可能是更好的选择。
  • 解决互斥的依赖冲突:另一个经常看到通过排除解决的情况是两个依赖项不能一起使用,因为它们代表了相同事物的两个实现(相同功能)。一些常见的例子是冲突的日志记录API实现(如log4j和log4j-over-slf4j)或在不同版本中具有不同坐标的模块(如com.google.collections和guava)。在这些情况下,如果Gradle不知道此信息,建议通过组件元数据规则添加缺失的功能信息,如“声明组件能力”部分所述。即使您正在开发库,并且您的消费者将不得不再次解决冲突,将决策留给库的最终消费者通常是正确的解决方案。换句话说,作为库作者,您不应该决定您的消费者最终使用哪种日志记录实现。

升级传递依赖项的版本

直接依赖项与依赖约束

一个组件可能有两种不同类型的依赖关系:

  • 直接依赖项是组件直接需要的。直接依赖项也被称为一级依赖项。例如,如果项目源代码需要Guava,那么Guava应该被声明为直接依赖项。
  • 传递依赖项是您的组件所需的依赖项,但只是因为其他依赖项需要它们。

在依赖管理中,处理传递依赖项问题是非常常见的。通常开发人员会错误地通过添加直接依赖项来修复传递依赖项问题。为了避免这种情况,Gradle提供了依赖约束的概念。

在传递依赖项上添加约束

依赖约束允许您定义构建脚本中声明的依赖项和传递依赖项的版本版本范围

它是表达应该应用于配置的所有依赖项的约束的首选方法。当Gradle尝试解析到一个模块版本的依赖关系时,将考虑所有带有版本的依赖声明、所有传递依赖项以及该模块的所有依赖约束。选择满足所有条件的最高版本。如果找不到这样的版本,Gradle将失败,并显示冲突的声明。如果发生这种情况,您可以调整依赖项或依赖约束的声明,或者根据需要对传递依赖项进行其他调整。与依赖项声明类似,依赖约束声明也由配置限定,因此可以有选择地为构建的各个部分定义它们。如果依赖约束影响了解析结果,则之后仍然可以应用任何类型的依赖解析规则。

// Example 1. Define dependency constraints
dependencies {
    implementation 'org.apache.httpcomponents:httpclient'
    constraints {
        implementation('org.apache.httpcomponents:httpclient:4.5.3') {
            because 'previous versions have a bug impacting this application'
        }
        implementation('commons-codec:commons-codec:1.11') {
            because 'version 1.9 pulled from httpclient has bugs affecting this application'
        }
    }
}

在示例中,依赖声明中省略了所有版本信息

相反,版本信息在约束块中定义。对于commons-codec:1.11的版本定义只有在commons-codec作为传递依赖项引入时才会生效,因为在项目中没有定义commons-codec作为直接依赖项。否则,约束没有任何效果。依赖约束还可以定义丰富的版本约束,并支持严格版本以强制执行一个版本,即使与传递依赖项定义的版本相矛盾(例如,如果需要降级版本)。

在使用Gradle模块元数据时,依赖约束只有在发布和使用Gradle时才会被发布。这意味着当前仅在完全使用Gradle进行发布和消费时才完全支持它们(即在使用Maven或Ivy消费模块时它们会丢失)。

依赖约束本身也可以作为传递依赖项添加。

参考链接

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BigDataMLApplication

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

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

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

打赏作者

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

抵扣说明:

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

余额充值