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

为什么排除传递性依赖?

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

在排除之前,请确保

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

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

排除在以下情况有用:

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

  • 您需要减少依赖项的大小

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

步骤 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 功能,如依赖约束或元数据规则。

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

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

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