19、版本目录:libs.versions.toml、版本对齐、目录插件
版本管理,说白了就是项目里的「依赖大管家」。我见过太多项目,几十个模块各自定义版本号,升级一个库要改十几个文件,改完还经常漏掉。嗯,Gradle 7.0 引入的版本目录(Version Catalog)就是为了解决这个痛点。
19.1 为什么需要版本目录?
先说说我踩过的坑。曾经有个多模块项目,Spring Boot 版本分散在 6 个 build.gradle.kts 里。升级 2.6 到 2.7 时,我手动改了 5 个文件,结果漏掉了一个模块的测试依赖。编译没问题,运行时直接 ClassNotFoundException。你想想看,这种低级错误多耽误事。
版本目录的核心价值就三点:
- 统一管理:所有版本号集中在一个文件里
- 类型安全:Kotlin DSL 里直接调用,编译期就能发现拼写错误
- 跨项目共享:可以发布到仓库,多个项目共用一套版本约束
核心思想:把版本号从构建脚本中剥离出来,让 build.gradle.kts 只关注「用什么」,不关心「用哪个版本」。
19.2 libs.versions.toml 文件结构
这个文件通常放在 gradle/ 目录下。我个人习惯叫它「版本清单」,因为它就像超市购物清单一样,把需要的东西列清楚就行。
# gradle/libs.versions.toml
[versions]
kotlin = "1.9.0"
coroutines = "1.7.3"
retrofit = "2.9.0"
okhttp = "4.11.0"
[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
[bundles]
networking = ["retrofit-core", "retrofit-gson", "okhttp-core"]
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
这个文件分四个区域:
| 区域 | 作用 | 示例 |
|---|---|---|
| [versions] | 定义版本号常量 | kotlin = "1.9.0" |
| [libraries] | 声明依赖坐标 | retrofit-core = { module = "...", version.ref = "retrofit" } |
| [bundles] | 将多个依赖打包 | networking = ["retrofit-core", "okhttp-core"] |
| [plugins] | 声明插件版本 | kotlin-jvm = { id = "...", version.ref = "kotlin" } |
小技巧:version.ref 引用的是 [versions] 里的键名。如果版本号直接写在 library 里,用 version = "1.0.0" 也行,但不推荐——失去了统一管理的意义。
19.3 在 build.gradle.kts 中使用版本目录
配置好 toml 文件后,在构建脚本里就能直接调用了。注意看,这里没有字符串,全是类型安全的访问器:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("gradle/libs.versions.toml"))
}
}
}
// build.gradle.kts
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
}
dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.bundles.networking)
// 也可以单独引用 bundle 里的某个库
testImplementation(libs.kotlinx.coroutines.core)
}
为什么说类型安全?因为 libs.kotlin.stdlib 如果拼写错误,IDE 直接标红,编译直接报错。我曾经在传统方式里把 kotlin-stdlib 写成 kotlin-stdlib-jdk8,编译过了,但运行时找不到类。用版本目录后,这种问题再也没出现过。
19.4 版本对齐:解决依赖冲突
版本对齐,说白了就是「让所有模块用同一个版本的库」。举个例子:模块 A 依赖 OkHttp 4.10,模块 B 依赖 OkHttp 4.11,最终打包时到底用哪个?
Gradle 默认用最高版本,但这不一定安全。我遇到过 OkHttp 4.10 和 4.11 之间有个 API 被标记为 deprecated,结果运行时日志刷屏警告。
版本目录配合 平台依赖(Platform) 可以强制对齐:
// 在 toml 里定义平台
[libraries]
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version = "4.11.0" }
okhttp-core = { module = "com.squareup.okhttp3:okhttp" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor" }
// 在 build.gradle.kts 里导入 BOM
dependencies {
implementation(platform(libs.okhttp.bom))
implementation(libs.okhttp.core)
implementation(libs.okhttp.logging)
}
这样所有 OkHttp 相关依赖都强制使用 4.11.0 版本,不会出现版本漂移。
注意:版本对齐不是万能的。如果两个库依赖了同一个库的不同不兼容版本(比如 Guava 27 vs 30),BOM 也救不了你。这时候需要看是不是有传递依赖冲突,或者考虑用 Gradle 的强制版本(force)来解决。
19.5 目录插件:自动生成访问器
Gradle 7.0 之后,版本目录默认会生成类型安全的访问器。但如果你用的是 Gradle 6.x,或者想自定义访问器名称,可以用 目录插件(Version Catalog Plugin)。
我个人建议:只要项目 Gradle 版本 >= 7.0,直接用内置功能就行。目录插件更多是为了兼容旧版本或者做高级定制。
// 如果需要自定义,在 settings.gradle.kts 里配置
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("gradle/libs.versions.toml"))
// 自定义访问器名称
library("my-okhttp", "com.squareup.okhttp3", "okhttp").version("4.11.0")
}
}
}
访问器命名规则:
- 点号(.)和短横线(-)会被替换成点号
- 比如
kotlin-stdlib变成libs.kotlin.stdlib kotlinx-coroutines-core变成libs.kotlinx.coroutines.core
避坑指南:我曾经把 library 的 key 写成 kotlin-stdlib-jdk8,结果访问器变成了 libs.kotlin.stdlib.jdk8。虽然能用,但看着别扭。建议 key 的命名保持简洁,不要带版本后缀。
19.6 多项目共享版本目录
如果你在维护多个项目,可以把版本目录发布到仓库,让所有项目引用同一个版本清单。这样升级一个库时,改一个文件就能同步所有项目。
// 发布版本目录(在独立的发布项目中)
plugins {
`version-catalog`
`maven-publish`
}
catalog {
versionCatalog {
from(files("gradle/libs.versions.toml"))
}
}
publishing {
publications {
create("catalog") {
from(components["versionCatalog"])
}
}
}
// 其他项目引用
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from("com.example:catalog:1.0.0")
}
}
}
这种方式特别适合微服务架构。我参与过一个 20+ 模块的项目,每个模块独立发布,但版本目录统一管理。升级 Spring Boot 时,只需要改一个 toml 文件,重新发布目录,所有模块自动同步。
19.7 本章知识体系
下面这张图展示了版本目录的核心逻辑和流转关系:
从这张图可以看出,版本目录是整个依赖管理的枢纽。它从 toml 文件或远程仓库获取版本信息,然后通过类型安全的访问器注入到构建脚本中。版本对齐、目录插件、跨项目共享都是基于这个核心机制扩展出来的能力。
总结一下:版本目录不是银弹,但它解决了 80% 的版本管理痛点。我建议所有新项目都从第一天开始用,老项目也值得花半天时间迁移。相信我,当你下次升级依赖时,你会感谢这个决定的。
公众号:蓝海资料掘金营,微信deep3321