1、概览 {#1概览}
Gradle 是一款构建自动化工具,用于管理和自动化应用程序的构建、测试和部署过程。
使用基于 Groovy 或 Kotlin 的 DSL(Domain-Specific Language)来定义构建任务,可以轻松地自动定义和管理项目中所需的依赖。
本文将带你了解在 Gradle 中排除传递依赖的几种方法。
2、传递依赖是什么? {#2传递依赖是什么}
假设我们使用的 A 依赖于另一个库 B 。默认情况下,当我们包含 A 时,Gradle 会自动将 B 添加到项目的 classpath 中,这样,即使我们没有明确地将 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 自动添加了它们,因为 Google Guava 需要它们。
不过,有时我们也有充分的理由排除传递依赖。
3、为什么要排除传递依赖? {#3为什么要排除传递依赖}
如下是排除传递依赖的几个原因:
- 避免安全问题:例如,Firestore Firebase SDK 24.4.0 或 Dagger 2.44 与 Google Guava 31.1-jre 存在传递依赖关系,而后者存在 安全漏洞 问题。
- 避免不必要的依赖:有些库可能会带来与我们的应用无关的依赖。
- 减小应用体积:通过排除未使用的传递依赖,我们可以减少打包到应用中的库数量,从而减小输出文件(JAR、WAR、APK)的大小。我们还可以使用 ProGuard 等工具,通过移除未使用的代码、优化字节码、混淆类和方法名称以及移除不必要的资源,来大幅减小应用的大小。在不牺牲功能的前提下,这一过程可使应用程序更小、更快、更高效。
因此,Gradle 也提供了排除依赖的机制。
3.1、解决版本冲突 {#31解决版本冲突}
不建议通过排除传递依赖来解决版本冲突,因为 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 版。
我们可以声明具有特定版本或 版本范围 的依赖,以定义我们项目可以使用的依赖的可接受版本。
4、排除传递依赖 {#4排除传递依赖}
4.1、排除 Group {#41排除-group}
当我们定义一个依赖,例如 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 Group,该 Group 包含 guava 、failureaccess 和 listenablefuture 模块:
dependencies {
// ...
implementation ('com.google.guava:guava:31.1-jre') {
exclude group: 'com.google.guava'
}
}
这将排除 com.google.guava Group 中除 guava 之外的所有模块,因为 guava 是主模块。
4.2、排除特定模块 {#42排除特定模块}
要排除特定的模块依赖,我们可以使用目标路径。例如,当我们使用 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、排除多个模块 {#43排除多个模块}
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 工具包依赖关系中排除了多个模块,以避免包含某些默认情况下已包含在项目中的模块。
4.4、排除所有传递模块 {#44排除所有传递模块}
有时,我们可能只需要使用主模块,而不需要任何其他依赖项。或者,我们需要明确指定所使用的每个依赖(传递)的版本。
transitive = false
语句会告诉 Gradle 不要自动包含我们使用的库中的传递依赖:
dependencies {
// ...
implementation('org.hibernate.orm:hibernate-core:7.0.0.Beta1') {
transitive = false
}
}
这意味着只有 Hibernate Core 本身会被添加到项目中,而不会有任何其他依赖项。
4.5、在每个配置中排除 {#45在每个配置中排除}
除了在依赖关系声明中排除传递依赖外,还可以在配置级别排除传递依赖。
我们可以通过 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 Group 中的 byte-buddy-agent 模块。
4.6、在特定配置中排除 {#46在特定配置中排除}
有时,我们可能需要通过特定配置来排除依赖:
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 允许以这种方式排除依赖。我们可以在 classpath 中对特定配置使用 exclude ,如 testImplementation 、testCompileClasspath 、annotationProcessor 等。
5、总结 {#5总结}
排除传递依赖有三个主要原因:避免安全问题、避免使用不需要的库以及减少打包后应用的大小。
本文介绍了在 Gradle 中排除传递依赖的各种方法,从按 Group、特定模块或多个模块排除,到排除所有传递依赖模块。
Ref:https://www.baeldung.com/gradle-exclude-transitive-dependencies