1. 概述
Gradle 是一款构建自动化工具,用于管理和自动化应用程序的构建、测试和部署流程。
它基于 Groovy 或 Kotlin 的领域特定语言(DSL)定义构建任务,使我们能轻松定义和管理项目所需的库依赖。
本教程将重点讨论在Gradle中排除传递性依赖的几种方法。
2. 什么是传递性依赖?
假设我们使用库A,而库A又依赖库B。那么B就是A的传递性依赖。默认情况下,当我们引入A时,Gradle会自动将B添加到项目的类路径中,这样我们就能在项目中使用B的代码,即使我们没有显式声明对B的依赖。
举个实际例子,我们在项目中引入 Google Guava:
dependencies {
// ...
implementation 'com.google.guava:guava:31.1-jre'
}
如果Google Guava依赖其他库,Gradle会自动引入这些库。
要查看项目中的所有依赖,可以运行:
./gradlew <module-name>:dependencies
以模块 excluding-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因为Guava需要它们而自动添加。
但有时我们可能有充分理由排除这些传递性依赖。
3. 为什么要排除传递性依赖?
以下是几个常见原因:
- 规避安全风险:例如 Firestore Firebase SDK 24.4.0 或 Dagger 2.44 传递性依赖 Google Guava 31.1-jre,而该版本存在安全漏洞。
- 避免无用依赖:某些库可能引入与应用无关的依赖。⚠️ 需谨慎决策——比如当需要完全排除某个传递性依赖(无论版本)时。
- 减小应用体积:排除未使用的传递性依赖可减少打包到应用中的库数量,从而缩小输出文件(JAR/WAR/APK)体积。还可结合 ProGuard 等工具,通过移除未使用代码、优化字节码、混淆类名和移除无用资源进一步减小体积。
Gradle为此提供了依赖排除机制。
3.1. 解决版本冲突
❌ 不推荐通过排除传递性依赖解决版本冲突,因为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 <-------------------+ 相同版本
| +--- 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 +-------------------------------+ 最新版本
| +--- 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 +-----+ 最新版本
| | +--- org.eclipse.angus:angus-activation:2.0.0
例如 org.jboss.logging:jboss-logging:3.5.0.Final 出现两次,因版本相同只保留一份。而 jakarta.activation:jakarta.activation-api 存在 2.1.0 和 2.1.1 两个版本,Gradle选择最新版 2.1.1。io.smallrye:jandex 同理选择 3.2.0。
但有时我们不想用最新版,可强制指定版本:
implementation("io.smallrye:jandex") {
version {
strictly '3.1.2'
}
}
即使存在更新版本,Gradle仍会使用 3.1.2。
也可通过版本范围声明可接受的依赖版本。
4. 排除传递性依赖
我们可在多种场景下排除传递性依赖。下面结合常见库实例说明。
4.1. 按组排除
依赖声明格式如下(以Guava为例):
com.google.guava : guava : 31.1-jre
---------------- ----- --------
^ ^ ^
| | |
组(group) 模块(module) 版本(version)
根据第2节的输出,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 外的所有模块。
4.2. 排除特定模块
要排除特定模块,需指定组名和模块名。例如使用 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,该模块也不会被引入项目。
4.3. 排除多个模块
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 组下的三个模块。
类似地,可排除更多模块(如Google ML Kit的冗余依赖):
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'
}
}
此操作可避免重复引入项目中已存在的模块。
4.4. 排除所有传递性模块
有时我们只需主模块,或想显式控制每个依赖的版本。
使用 transitive = false
告知Gradle不自动引入传递性依赖:
dependencies {
// ...
implementation('org.hibernate.orm:hibernate-core:7.0.0.Beta1') {
transitive = false
}
}
这样只会添加Hibernate Core本身,不包含其任何依赖。
4.5. 全局配置排除
除在依赖声明中排除,还可在配置(configuration)层面全局排除。
使用 configurations.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。
4.6. 特定配置排除
有时需要针对特定配置排除依赖。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'
}
可在 testImplementation、testCompileClasspath、annotationProcessor 等特定配置的类路径中使用 exclude
。
5. 总结
排除传递性依赖主要有三个原因:规避安全风险、移除无用依赖、减小应用体积。
本文介绍了Gradle中排除传递性依赖的多种方式:按组排除、排除特定模块、排除多个模块、排除所有传递性模块,以及在配置层面全局或特定排除。但需谨慎决策,避免过度排除导致运行时问题。
完整源代码见GitHub仓库。