依赖声明与仓库配置

这一章,我们来聊聊 Gradle 里最基础也最容易出错的两个东西:repositories 和 dependencies。说白了,就是告诉 Gradle:你去哪儿找包?你要什么包?

我刚开始用 Gradle 时,觉得这两块就是写几行代码的事。后来被坑了几次才明白,顺序、写法、仓库选择,每个细节都有可能让构建慢得像蜗牛,或者直接失败。嗯,咱们一个一个拆开说。

仓库配置 —— repositories 块

仓库就是依赖的“货源”。你声明了依赖,Gradle 会按仓库列表的顺序逐个去找。

先看看最常见的仓库配置长什么样:

repositories {
    mavenCentral()
    google()
    maven { url 'https://jitpack.io' }
    mavenLocal()
    flatDir { dirs 'libs' }
}

这里有几个要点:

  • mavenCentral() —— Maven Central,全球最大的 Java 仓库。我建议所有项目都至少配它。稳定,靠谱。
  • google() —— Android 开发必备。存放 AndroidX、Material 等官方库。
  • jcenter() —— 曾经很流行,但 2021 年已经停止服务。如果你的项目还在用,赶紧换掉。我记得有个老项目一直卡在构建上,查了半天发现是 jcenter 返回了 404 但 Gradle 还在傻等超时。从那以后我再也不用它了。
  • maven { url '...' } —— 自定义 Maven 仓库,比如内部私服、JitPack、阿里云镜像。
  • mavenLocal() —— 本地 ~/.m2 仓库。适合本地调试自己发布的包。
  • flatDir —— 直接引用本地 jar 文件目录。我用得少,但有时接手遗留项目会看到。
注意:仓库顺序影响性能。能命中缓存的仓库放前面,慢的或容易超时的放后面。我曾经把 mavenCentral 放在最后,结果每次构建都要等阿里云镜像超时后才去中央仓库,多花了两分钟才意识到问题。

下面这个表格可以帮大家快速选择:

仓库 适用场景 注意事项
mavenCentral() 几乎任何 Java/Kotlin 项目 速度一般,建议配镜像
google() Android 项目 必须配合 mavenCentral
mavenLocal() 本地发布调试 只用于开发机,不要提交到 CI
自定义 Maven 公司私服、镜像、快照库 需要确保网络可达
jcenter() 放弃,不要再使用 已经只读,随时可能彻底关闭
flatDir 本地 libs 目录管理 不推荐大规模使用

依赖声明 —— dependencies 块

仓库是“超市”,依赖就是你的“购物清单”。Gradle 支持多种依赖坐标写法,但最常用的是「组:名称:版本」这种三要素格式。

dependencies {
    implementation 'com.google.guava:guava:33.0.0-jre'
    testImplementation 'junit:junit:4.13.2'
    compileOnly 'org.projectlombok:lombok:1.18.30'
    runtimeOnly 'ch.qos.logback:logback-classic:1.4.11'
    implementation project(':core')
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

这里每个配置(configuration)都有讲究:

  • implementation —— 最常用。依赖对模块外部不可见,编译更快。我建议新项目默认都用它。
  • api —— 会暴露给消费者。如果你的库把某个依赖当作公开 API 的一部分,用 api 而不是 implementation。
  • compileOnly —— 只在编译时需要,运行时不用。典型例子:Lombok、注解处理器。
  • runtimeOnly —— 编译不需要,运行时必须有。比如某个数据库驱动。
  • testImplementation —— 测试代码专用。JUnit、Mockito 放这里。
  • project() —— 依赖同一项目的另一个模块。
  • fileTree —— 依赖某个文件夹下的所有 jar。偶尔有用。
个人小建议:不要把所有依赖都一股脑写成 implementation。搞清楚依赖的作用范围可以加快增量编译。我处理过一个项目,所有依赖都写 api,导致改一行代码要重新编译大半个项目,后来改成 implementation,编译时间缩短了 60%。

依赖管理与版本统一

项目大了,同一个依赖可能在多个模块出现。版本写得到处是,改起来很痛苦。Gradle 给了我们几个好工具。

1. BOM(Bill of Materials)

用 Spring Boot 或 Jackson 等库时,可以直接导入它们的 BOM,省去自己写版本。

implementation platform('org.springframework.boot:spring-boot-dependencies:3.2.0')
implementation 'org.springframework.boot:spring-boot-starter-web'
// 版本由 BOM 统一管理,无需写版本号

2. Version Catalog(Gradle 7.0+)

Gradle 官方推荐的方式。在 gradle/libs.versions.toml 里集中定义版本和依赖,然后在 build.gradle 里引用。

// libs.versions.toml
[versions]
guava = "33.0.0-jre"

[libraries]
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }

// build.gradle
dependencies {
    implementation libs.guava
}

为什么我喜欢 Version Catalog?它让依赖的改动变得特别清晰,而且 IDE 会自动补全。你想想看,一个 20 模块的项目,要在每个 build.gradle 里改同一个版本号有多容易出错。用 catalog 后,我只改一行。

避坑指南(都是血泪史)

我在项目中踩过不少坑,挑几个典型的说说:

  1. 仓库顺序导致构建缓慢。 我曾经在 build.gradle 里把 maven { url 'http://slow-mirror.com' } 写在了 mavenCentral 前面,结果每次构建都要等镜像超时(足足 30 秒)才去中央仓库。后来我把快的放前面,构建时间直接砍半。
  2. 漏写仓库。 有次我引入了一个第三方库,但它依赖的某个 transitive 依赖只存在于 JitPack。我没配 JitPack 仓库,Gradle 找半天找不到,最后抛异常。排查了半天才发现… 嗯,从那以后我写依赖之前会先看一眼它的文档里强调了哪个仓库。
  3. implementation 误用成 api。 上面提到了,编译时间暴涨。还有一个更隐蔽的问题:api 会让依赖泄露到外部,如果外部模块依赖了你内部的一个私有库的版本,就会出现莫名其妙的冲突。我帮同事 debug 过这个,最后把 api 改成 implementation 就解决了。
  4. 版本号写死 vs 动态版本。 有人喜欢用 30.+ 这种动态版本号,觉得省事。但动态版本每次构建都可能解析到不同版本,导致“昨天能构建今天就不能”的玄学问题。我个人的习惯是:除了开发初期,一律用固定版本。
一句话总结:仓库配好顺序,依赖分对配置,版本集中管理。这三步做好,大部分依赖问题都能避免。
依赖声明与仓库配置 核心流程 项目开始 配置 repositories(仓库列表) 常见仓库(按推荐顺序排列) mavenCentral() google() 自定义 Maven mavenLocal() 配置 dependencies(依赖列表) Gradle 解析并下载依赖 构建成功 ✓ 仓库顺序影响速度 依赖配置影响编译性能

以上就是依赖声明与仓库配置的核心内容。记住,好的配置习惯能让你少熬夜 debug。下次遇到依赖问题,先检查仓库顺序,再检查依赖配置,大概率能搞定。