第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 配置作用域可视化

下图以矩阵形式展示四种主要配置在不同阶段的作用范围。绿色表示“生效”,红色表示“不生效”。

Gradle依赖配置作用域矩阵 implementation api compileOnly runtimeOnly 编译期依赖 运行期依赖 消费者编译期 消费者运行期 Y Y Y N Y Y N Y N Y N N N Y N N Y = 该配置在此作用域中包含此依赖;N = 不包含。消费者指依赖本模块的下游模块。

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 配置进一步细化。