Gradle是一个构建自动化工具,用于管理和自动化构建、测试和部署应用程序的过程。
使用基于Groovy或Kotlin 的领域特定语言 (DSL)定义构建任务,可以轻松自动定义和管理项目中所需的库依赖项。
在本教程中,我们将具体讨论在 Gradle 中排除传递依赖的几种方法。
排除传递依赖项有三个主要原因:避免安全问题、避免我们不需要的库以及减小应用程序大小。
在本文中,我们讨论在 Gradle 中排除传递依赖项的方法,从按组、特定模块或多个模块排除,到排除所有传递模块。我们还可以在配置级别进行排除。但是,排除必须经过明智而谨慎的考虑。
什么是传递依赖?
假设我们使用一个依赖于另一个库 B 的库 A 。那么B 被称为 A 的传递依赖。默认情况下,当我们包含 A 时,Gradle 会自动将 B 添加到我们项目的类路径中,这样来自 B 的代码也可以在我们的项目中使用,即使我们没有明确地将其添加为依赖项。
为了更清楚起见,我们使用一个真实的例子,在我们的项目中定义Google Guava :
dependencies { // ... implementation 'com.google.guava:guava:31.1-jre' }
|
如果 Google Guava 与其他库有依赖关系,那么 Gradle 将自动包含这些其他库。
要查看我们在项目中使用的依赖项,我们可以打印它们:
./gradlew <module-name>:dependencies
|
在这种情况下,我们使用一个名为clusion-transitive-dependencies的模块:
./gradlew excluding-transitive-dependencies:dependencies
|
让我们看看输出:
testRuntimeClasspath - Runtime classpath of source set 'test'. \--- com.google.guava:guava:31.1-jre +--- com.google.guava:failureaccess:1.0.1 +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava +--- com.google.code.findbugs:jsr305:3.0.2 +--- org.checkerframework:checker-qual:3.12.0 +--- com.google.errorprone:error_prone_annotations:2.11.0 \--- com.google.j2objc:j2objc-annotations:1.3
|
我们可以看到一些我们没有明确定义的库,但是 Gradle 自动添加了它们,因为 Google Guava 需要它们。
然而,有时我们可能有充分的理由排除传递依赖关系。
为什么要排除传递依赖?
让我们回顾一下为什么我们可能想要排除传递依赖关系的几个充分理由:
- 避免安全问题:例如,Firestore Firebase SDK 24.4.0 或 Dagger 2.44 对 Google Guava 31.1-jre 具有传递依赖性,而后者存在安全漏洞问题。
- 避免不必要的依赖:某些库可能会带来与我们的应用程序无关的依赖项。但是,应该明智而谨慎地考虑这一点——例如,当我们需要完全排除传递依赖项时,无论版本号如何。
- 减小应用程序大小:通过排除未使用的传递依赖项,我们可以减少打包到应用程序中的库数量,从而减小输出文件(JAR、WAR、APK)的大小。我们还可以使用ProGuard等工具,通过删除未使用的代码、优化字节码、混淆类和方法名称以及删除不必要的资源来显著减小应用程序的大小。此过程可使应用程序更小、更快、更高效,而不会牺牲功能。
因此,Gradle还提供了排除依赖项的机制。
解决版本冲突
我们不建议排除传递依赖来解决版本冲突,因为Gradle 已经有很好的机制来处理这个问题。
当有两个或多个相同的依赖项时,Gradle 只会选择一个。如果它们有不同的版本,默认情况下,它会选择最新版本。 如果我们仔细观察,就会在日志中看到这种行为:
+--- org.hibernate.orm:hibernate-core:7.0.0.Beta1 | +--- jakarta.persistence:jakarta.persistence-api:3.2.0-M2 | +--- jakarta.transaction:jakarta.transaction-api:2.0.1 | +--- org.jboss.logging:jboss-logging:3.5.0.Final <-------------------+ same version | +--- org.hibernate.models:hibernate-models:0.8.6 | | | +--- io.smallrye:jandex:3.1.2 -> 3.2.0 <------------------+ | | | \--- org.jboss.logging:jboss-logging:3.5.0.Final +-----------|--+ | +--- io.smallrye:jandex:3.2.0 +-------------------------------+ latest version | +--- com.fasterxml:classmate:1.5.1 | | \--- jakarta.activation:jakarta.activation-api:2.1.0 -> 2.1.1 <---+ | +--- org.glassfish.jaxb:jaxb-runtime:4.0.2 | | | \--- org.glassfish.jaxb:jaxb-core:4.0.2 | | | +--- jakarta.xml.bind:jakarta.xml.bind-api:4.0.0 (*) | | | +--- jakarta.activation:jakarta.activation-api:2.1.1 +-----+ latest version | | +--- org.eclipse.angus:angus-activation:2.0.0
|
我们可以看到,一些识别出的依赖项是相同的。例如,org.jboss.logging:jboss-logging:3.5.0.Final出现了两次,但由于它们是同一个版本,因此 Gradle 只会包含一份。
同时,对于jakarta.activation:jakarta.activation-api,有两个版本 - 2.1.0 和 2.1.1。Gradle 将选择最新版本,即 2.1.1。对于io.smallrye:jandex也是如此,它将选择 3.2.0。
但有时我们不想使用最新版本。我们可以强制 Gradle 选择我们严格需要的版本:
implementation("io.smallrye:jandex") { version { strictly '3.1.2' } }
|
这里,即使找到另一个甚至更新的版本,Gradle 仍然会选择 3.1.2 版本。
我们可以声明具有特定版本或版本范围的依赖项来定义我们的项目可以使用的依赖项的可接受版本。
排除传递依赖
我们可以在各种情况下排除传递依赖关系。为了更清楚、更容易理解,我们将使用我们可能熟悉的库的真实示例。
1. 排除群组
当我们定义依赖项时,例如 Google Guava,如果我们查看,依赖项具有如下格式:
com.google.guava : guava : 31.1-jre ---------------- ----- -------- ^ ^ ^ | | | group module version
|
如果我们查看第 2 部分中的输出,我们会看到 Google Guava 依赖的五个模块。它们是com.google.code.findbugs、com.google.errorprone、com.google.guava、com.google.j2objc和org.checkerframework。
我们将排除com.google.guava 组,其中包含guava、FailureAccess和listenablefuture模块:
dependencies { // ... implementation ('com.google.guava:guava:31.1-jre') { exclude group: 'com.google.guava' } }
|
这将排除com.google.guava组中除guava之外的所有模块,因为它是一个主模块。
排除特定模块
要排除特定的模块依赖项,我们可以使用目标路径。例如,当我们使用Hibernate库时,我们只需要排除org.glassfish.jaxb:txw2模块:
dependencies { // ... implementation ('org.hibernate.orm:hibernate-core:7.0.0.Beta1') { exclude group: 'org.glassfish.jaxb', module : 'txw2' } }
|
这意味着即使 Hibernate 依赖于txw2模块,我们也不会将该模块包含在项目中包含。
排除多个模块
Gradle 还允许我们在单个依赖语句中排除多个模块:
dependencies { // ... testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation ('org.junit.jupiter:junit-jupiter') { exclude group: 'org.junit.jupiter', module : 'junit-jupiter-api' exclude group: 'org.junit.jupiter', module : 'junit-jupiter-params' exclude group: 'org.junit.jupiter', module : 'junit-jupiter-engine' } }
|
在此示例中,我们从org.junit-jupiter依赖项中排除了junit-jupiter-api、junit-jupiter-params和junit-jupiter-engine模块。
通过这种机制,我们可以对更多的模块排除情况做同样的事情:
dependencies { // ... implementation('com.google.android.gms:play-services-mlkit-face-detection:17.1.0') { exclude group: 'androidx.annotation', module: 'annotation' exclude group: 'android.support.v4', module: 'core' exclude group: 'androidx.arch.core', module: 'core' exclude group: 'androidx.collection', module: 'collection' exclude group: 'androidx.coordinatorlayout', module: 'coordinatorlayout' exclude group: 'androidx.core', module: 'core' exclude group: 'androidx.viewpager', module: 'viewpager' exclude group: 'androidx.print', module: 'print' exclude group: 'androidx.localbroadcastmanager', module: 'localbroadcastmanager' exclude group: 'androidx.loader', module: 'loader' exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel' exclude group: 'androidx.lifecycle', module: 'lifecycle-livedata' exclude group: 'androidx.lifecycle', module: 'lifecycle-common' exclude group: 'androidx.fragment', module: 'fragment' exclude group: 'androidx.drawerlayout', module: 'drawerlayout' exclude group: 'androidx.legacy.content', module: 'legacy-support-core-utils' exclude group: 'androidx.cursoradapter', module: 'cursoradapter' exclude group: 'androidx.customview', module: 'customview' exclude group: 'androidx.documentfile.provider', module: 'documentfile' exclude group: 'androidx.interpolator', module: 'interpolator' exclude group: 'androidx.exifinterface', module: 'exifinterface' } }
|
此示例从 Google ML Kit 依赖项中排除了各种模块,以避免包含默认情况下已包含在项目中的某些模块。
排除所有传递模块
有时我们可能只需要使用主模块而不使用任何其他依赖项。或者有时我们需要明确指定所使用的每个依赖项的版本。
transitive = false语句将告诉 Gradle 不要自动包含我们使用的库中的传递依赖项:
dependencies { // ... implementation('org.hibernate.orm:hibernate-core:7.0.0.Beta1') { transitive = false } }
|
这意味着只有 Hibernate Core 本身会被添加到项目中,而不需要任何其他依赖项。
从每个配置中排除
除了在依赖声明中排除传递依赖之外,我们还可以在配置级别这样做。
我们可以使用configuration.configureEach { },它使用给定的操作配置集合中的每个元素。
此方法在 Gradle 4.9 及更高版本中可用,作为all()的推荐替代。
我们马上尝试一下:
dependencies { // ... testImplementation 'org.mockito:mockito-core:3.+' } configurations.configureEach { exclude group: 'net.bytebuddy', module: 'byte-buddy-agent' }
|
这意味着我们将net.bytebuddy组的byte-buddy-agent模块从所有使用该依赖项的配置中排除。
在特定配置中排除
有时我们需要通过针对特定配置来排除依赖项。 当然,Gradle 也允许这样做:
configurations.testImplementation { exclude group: 'org.junit.jupiter', module : 'junit-jupiter-engine' } configurations.testCompileClasspath { exclude group : 'com.google.j2objc', module : 'j2objc-annotations' } configurations.annotationProcessor { exclude group: 'com.google.guava' }
|
是的,Gradle 允许以这种方式排除依赖项。我们可以在类路径中使用exclude来获取特定配置,例如testImplementation、testCompileClasspath、annotationProcessor等。