第2章 Gradle依赖配置体系:implementation、api、compileOnly、runtimeOnly等配置差异
Gradle从3.0版本开始引入新的依赖配置体系,废弃了旧有的compile指令。开发者必须在implementation、api、compileOnly、runtimeOnly等配置中做出选择。每一种配置都定义了依赖在编译期、运行期以及下游消费者中的可见性与传播规则。合理使用这些配置能显著缩短增量编译时间、降低模块耦合,是构建优化的第一道关卡。
2.1 核心配置对比
| 配置名称 | 编译期作用域 | 运行期作用域 | 是否传递至消费者编译期 | 是否传递至消费者运行期 | 典型使用场景 |
|---|---|---|---|---|---|
| implementation | 当前模块 | 当前模块 | 否 | 否 | 模块内部实现细节,不暴露给外部 |
| api | 当前模块 | 当前模块 | 是 | 是 | 接口或公共类依赖,必须暴露给消费者 |
| compileOnly | 当前模块 | 不提供 | 否 | 否 | 仅在编译时使用(如注解处理器、编译时校验) |
| runtimeOnly | 不提供 | 当前模块 | 否 | 否 | 仅在运行时需要(如数据库驱动、日志实现) |
| testImplementation | test编译期 | test运行期 | — | — | 测试代码独享的依赖 |
2.2 配置作用域可视化
下图以矩阵形式展示四种主要配置在不同阶段的作用范围。绿色表示“生效”,红色表示“不生效”。
2.3 各配置详解与代码示例
2.3.1 implementation
implementation 是默认推荐配置。声明的依赖仅在当前模块的编译期和运行期可用,不会泄漏给任何消费者。这是实现 最小化编译耦合 的核心手段。当模块的内部实现发生变化时,只有该模块自身需要重新编译,消费者无需重编,从而大幅缩短增量构建时间。
dependencies {
implementation 'com.google.guava:guava:33.0.0-jre'
implementation project(':internal-util')
}
2.3.2 api
api 会将依赖暴露给消费者的编译期和运行期。仅当该依赖中的类型直接出现在当前模块的公共接口/类的签名中时才应使用,否则应优先选择 implementation。滥用 api 会导致构建图中的模块紧密耦合,增加重新编译的范围。
// 只适用于暴露接口或基类
api 'com.fasterxml.jackson.core:jackson-databind:2.15.0'
api project(':common-dto')
2.3.3 compileOnly
依赖仅在编译期可用,运行时不包含。适合注解处理器(annotationProcessor 的替代)、编译时校验库、或容器/框架已提供的库(如 Servlet API)。注意:若在运行时通过反射调用该库,将出现 ClassNotFoundException。
compileOnly 'javax.servlet:servlet-api:2.5'
compileOnly 'org.projectlombok:lombok:1.18.30'
提示:对于 Lombok 等需要运行注解处理器的依赖,Gradle 5.4+ 推荐使用独立的
annotationProcessor 配置。两者不会冲突,但 compileOnly 仅保证编译期存在,而 annotationProcessor 专门用于处理注解生成代码。
2.3.4 runtimeOnly
依赖仅在运行期使用,编译期不被引用。常用于数据库驱动(JDBC)、日志实现等。可避免编译期误引用仅运行时依赖的类。
runtimeOnly 'org.postgresql:postgresql:42.6.0'
runtimeOnly 'ch.qos.logback:logback-classic:1.4.14'
2.3.5 testImplementation 及其他测试配置
testImplementation 仅在测试代码的编译和运行阶段生效。类似地,testRuntimeOnly 仅用于测试运行期,testCompileOnly 用于测试编译期。这些配置不会影响主代码,也从不传递。
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
2.4 性能与最佳实践
警惕 api 滥用: 如果一个模块的
api 依赖过多,消费者模块在编译时会被迫引入大量间接依赖,不仅导致编译时间增加,还可能引发版本冲突。重构时优先缩小暴露范围:从 api 改为 implementation,并观察是否仍有编译错误。
- 默认使用 implementation:除非该依赖必须出现在消费者的类路径上,否则一律使用
implementation。 - 定期检查依赖范围:利用
gradle dependencies命令查看各配置的依赖树,识别可以降级为implementation的api依赖。 - 区分 compileOnly 与 annotationProcessor:对于运行时不需要的编译注解处理,优先使用
annotationProcessor,它不会将依赖加入 classpath。 - 利用运行时传递性:
runtimeOnly阻止编译期泄漏,适合按需加载的服务实现。 - 大型多模块项目:为每个模块的
build.gradle建立依赖配置审计清单,严格控制api数量,可结合 Gradle 的apiElements和runtimeElements配置进一步细化。