本指南解释了如何在不需要或导致冲突时,从项目中排除传递性依赖

为什么要排除传递性依赖?

排除传递性依赖应该是一个有意识的决定,因为如果库依赖于它们,移除所需的依赖可能会导致运行时错误

在排除之前,请确保

  • 您的应用程序不需要被排除的依赖。

  • 您有足够的测试覆盖率来验证排除该依赖不会破坏功能。

排除在以下情况下很有用

  • 库包含您的应用程序不需要的不必要的传递性依赖

  • 您需要减小依赖的大小

  • 存在依赖冲突并且必须以不同方式解决。

步骤 1:使用 exclude() 排除传递性依赖

首先,您必须找到导致使用不需要的传递性依赖的依赖。您可以使用 dependencies 任务来完成此操作。

在此示例中,我们想要移除 commons-collections 依赖

build.gradle.kts
dependencies {
    implementation("commons-beanutils:commons-beanutils:1.9.4")
}
build.gradle
dependencies {
    implementation("commons-beanutils:commons-beanutils:1.9.4")
}

运行 ./gradlew dependencies --configuration runtimeClasspath 展示了 commons-collections 是如何由 commons-beanutils 引入的

dependencies.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_exclude_transitive_dependencies'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
\--- commons-beanutils:commons-beanutils:1.9.4
     +--- commons-logging:commons-logging:1.2
     \--- commons-collections:commons-collections:3.2.2

您可以通过在 exclude() 规则中指定 groupmodule 属性,为每个依赖声明排除传递性依赖

build.gradle.kts
dependencies {
    implementation("commons-beanutils:commons-beanutils:1.9.4") {
        exclude(group = "commons-collections", module = "commons-collections")
    }
}
build.gradle
dependencies {
    implementation("commons-beanutils:commons-beanutils:1.9.4") {
        exclude group: "commons-collections", module: "commons-collections"
    }
}
  • 这会从 commons-beanutils 的传递性依赖中移除 commons-collections

  • 排除仅适用于此特定依赖(即 commons-beanutils)。

运行./gradlew dependencies --configuration runtimeClasspath显示结果

dependencies-excluded.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_exclude_transitive_dependencies'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
\--- commons-beanutils:commons-beanutils:1.9.4
     \--- commons-logging:commons-logging:1.2

如果您的应用程序仅使用库的子集,并且该子集不需要被排除的依赖,则此方法是安全的。

步骤 2:了解排除的影响

排除仅在所有依赖声明都同意排除时才适用。如果您的项目中另一个依赖仍然需要被排除的依赖,Gradle 将不会排除它

build.gradle.kts
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' but does NOT exclude 'commons-collections'
}
build.gradle
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' but does NOT exclude 'commons-collections'
}

在这种情况下,commons-collections 仍然包含,因为 opencsv 将其带回。

运行./gradlew dependencies --configuration runtimeClasspath显示结果

dependencies-extra.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_exclude_transitive_dependencies'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
+--- commons-beanutils:commons-beanutils:1.9.4
|    +--- commons-logging:commons-logging:1.2
|    \--- commons-collections:commons-collections:3.2.2
\--- com.opencsv:opencsv:4.6
     +--- org.apache.commons:commons-lang3:3.8.1
     +--- org.apache.commons:commons-text:1.3
     |    \--- org.apache.commons:commons-lang3:3.7 -> 3.8.1
     +--- commons-beanutils:commons-beanutils:1.9.3 -> 1.9.4 (*)
     \--- org.apache.commons:commons-collections4:4.2

完全排除 commons-collections,您还必须从 opencsv 中排除它。

步骤 3:为多个依赖排除传递性依赖

对于任何其他依赖,您必须重复步骤 1

build.gradle.kts
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")
        exclude(group = "org.apache.commons", module = "commons-collections4") // Watch out for other transitive dependency creep
    }
}
build.gradle
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"
        exclude group: "org.apache.commons", module: "commons-collections4" // Watch out for other transitive dependency creep
    }
}

此示例实际上更进一步,因为 com.opencsv:opencsv 实际上引入了 commons-collections4

现在 commons-collectionscommons-collections4 已从所有引用它们的依赖中完全排除

dependencies-extra-excluded.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_exclude_transitive_dependencies'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
+--- commons-beanutils:commons-beanutils:1.9.4
|    \--- commons-logging:commons-logging:1.2
\--- com.opencsv:opencsv:4.6
     +--- org.apache.commons:commons-lang3:3.8.1
     +--- org.apache.commons:commons-text:1.3
     |    \--- org.apache.commons:commons-lang3:3.7 -> 3.8.1
     \--- commons-beanutils:commons-beanutils:1.9.3 -> 1.9.4 (*)

总结

  • 仅在必要时才使用排除以避免运行时错误。

  • 确保所有依赖都同意排除才能使其生效。

  • 在排除之前,考虑使用替代的 Gradle 功能,如依赖约束或元数据规则。

    • 使用依赖约束 - 如果发生依赖版本冲突,使用约束调整版本,而不是完全排除依赖。

    • 应用组件元数据规则 - 如果依赖在元数据中声明不正确(例如,包含不必要的编译时依赖),您可以组件元数据规则中移除依赖而不是排除它。

    • 解决互斥的依赖冲突 - 如果多个依赖因为它们代表相同功能的不同实现而冲突(例如,log4jlog4j-over-slf4j),最好定义组件功能而不是排除一个实现。