版本目录:Version Catalog 介绍
说到多模块项目的版本管理,我早期吃过不少苦头。那时候每个模块的 build.gradle 里都散落着各种版本号,升级一个依赖就得全局搜索替换,稍不留神就漏掉一个。后来 Gradle 7.0 推出了 Version Catalog,说白了就是给所有依赖版本号找了个「中央仓库」。
Version Catalog 的核心思想很简单:把依赖的坐标和版本号集中管理。你想想看,一个大型项目几十个模块,每个模块都写一遍 implementation 'com.google.guava:guava:31.1-jre',哪天 Guava 升级了,你得改多少个文件?用 Catalog 之后,只需要改一个地方。
Version Catalog 解决了三个核心痛点:
- 版本号散落各处,升级困难
- 不同模块可能引入同一依赖的不同版本,导致冲突
- 新人接手项目,搞不清哪些依赖是「官方推荐」的
我个人习惯把 Version Catalog 看作是「依赖的清单」。它不负责下载,只负责告诉你:这个项目里,某某依赖应该用哪个版本。真正干活的是 Gradle 的依赖解析引擎。
libs.versions.toml 配置详解
这个文件是 Version Catalog 的「宪法」。它通常放在 gradle/ 目录下,名字固定为 libs.versions.toml。嗯,这里要注意:文件名不能改,位置也不能改,Gradle 会默认去这里找。
文件结构分三块:versions、libraries、bundles。我直接给你看一个真实项目的例子:
# gradle/libs.versions.toml
[versions]
# 核心框架
spring-boot = "3.2.0"
spring-cloud = "2023.0.0"
mybatis-plus = "3.5.5"
# 工具库
guava = "32.1.3-jre"
hutool = "5.8.25"
lombok = "1.18.30"
# 测试
junit = "5.10.1"
mockito = "5.7.0"
[libraries]
# 格式:别名 = { module = "group:artifact", version.ref = "版本引用" }
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "spring-boot" }
mybatis-plus-spring-boot3 = { module = "com.baomidou:mybatis-plus-spring-boot3-starter", version.ref = "mybatis-plus" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
hutool-all = { module = "cn.hutool:hutool-all", version.ref = "hutool" }
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
# 也可以直接写死版本号(不推荐)
# fastjson = { module = "com.alibaba:fastjson", version = "2.0.45" }
[bundles]
# 依赖包:一组相关依赖的集合
spring-web = ["spring-boot-starter-web", "spring-boot-starter-test"]
testing = ["junit", "mockito"]
我的经验之谈:versions 块里版本号尽量用字符串,别用数字。我见过有人写 version = 3.2.0,结果 Gradle 把它当浮点数解析成 3.2,直接翻车。另外,libraries 块的别名建议用「-」连接,比如 spring-boot-starter-web,这样在构建脚本里引用时更自然。
这里有个细节:version.ref 引用的是 [versions] 里定义的键名。如果你某个依赖版本比较特殊,也可以直接在 libraries 里写死 version,但我个人不建议这么干——既然用了 Catalog,就统一管理到底。
在构建脚本中使用目录
配置好 toml 文件后,怎么在 build.gradle 里用呢?Gradle 会自动生成一个 libs 扩展对象,你直接点就行了。看代码:
// build.gradle.kts(Kotlin DSL)
dependencies {
implementation(libs.spring.boot.starter.web)
implementation(libs.guava)
implementation(libs.hutool.all)
// 使用 bundle
implementation(libs.bundles.testing)
// 编译期注解处理器
annotationProcessor(libs.lombok)
}
// 如果是 Groovy DSL,语法略有不同
// build.gradle
dependencies {
implementation libs.spring.boot.starter.web
implementation libs.guava
implementation libs.bundles.testing
}
你看,是不是很清爽?不用再写长长的 group:artifact:version 了。而且 IDE 会有自动补全,你输入 libs. 之后,所有可用的依赖都会列出来。
我曾经踩过的坑:一开始我把 toml 文件里的别名写成驼峰式,比如 springBootStarterWeb。结果在 Kotlin DSL 里引用时,libs.springBootStarterWeb 能工作,但 Groovy DSL 里死活找不到。后来查文档才发现,Gradle 会把别名中的「-」和「.」都映射成方法调用,所以统一用「-」最保险。
另外,如果你在子模块里想用 Catalog,不需要额外配置。只要根项目的 settings.gradle.kts 里启用了 Version Catalog(默认就启用),所有子模块都能直接使用 libs 对象。
多模块统一版本管理
这才是 Version Catalog 真正发力的场景。我参与过一个电商项目,有 20 多个子模块,每个模块都依赖 Spring Boot、MyBatis、Redis 等公共库。以前升级 Spring Boot 版本,得挨个改 20 个文件,改完还要检查有没有遗漏。用 Catalog 之后,只需要改 libs.versions.toml 里的一行。
来看一个多模块项目的典型结构:
my-project/
├── gradle/
│ └── libs.versions.toml # 版本目录
├── settings.gradle.kts
├── build.gradle.kts # 根项目(通常只做聚合)
├── common/
│ └── build.gradle.kts # 公共模块
├── service-user/
│ └── build.gradle.kts # 用户服务
├── service-order/
│ └── build.gradle.kts # 订单服务
└── web-api/
└── build.gradle.kts # API 网关
每个子模块的 build.gradle.kts 里,依赖写法完全一致:
// common/build.gradle.kts
dependencies {
implementation(libs.spring.boot.starter.web)
implementation(libs.mybatis.plus.spring.boot3)
implementation(libs.guava)
implementation(libs.hutool.all)
compileOnly(libs.lombok)
annotationProcessor(libs.lombok)
}
// service-user/build.gradle.kts
dependencies {
implementation(project(":common"))
implementation(libs.spring.boot.starter.web)
implementation(libs.mybatis.plus.spring.boot3)
testImplementation(libs.bundles.testing)
}
你发现没有?所有模块引用的 Spring Boot 版本都来自同一个地方。哪天 Spring Boot 出了 3.2.1,我只需要在 toml 文件里把 spring-boot = "3.2.1",然后重新构建,所有模块自动升级。
多模块版本管理的三个最佳实践:
- 公共依赖放 bundles:比如所有模块都要用的 spring-boot-starter-web、lombok,可以打包成一个 bundle,子模块一行引入。
- 版本号命名要有规律:我习惯用「库名-版本」的格式,比如 spring-boot、mybatis-plus,一眼就能看出是什么。
- 不要混用 Catalog 和直接声明:要么全部用 Catalog,要么不用。混着用会让新人困惑,也失去了统一管理的意义。
说到 bundles,我再补充一句。如果你有多个模块都依赖同一组库,比如每个 Web 模块都要引入 Spring Boot Web、Jackson、Validation,你可以这样:
# libs.versions.toml
[bundles]
web-common = [
"spring-boot-starter-web",
"jackson-databind",
"spring-boot-starter-validation"
]
# 子模块里
dependencies {
implementation(libs.bundles.web.common)
}
这样既减少了重复代码,又保证了依赖的一致性。我现在的项目里,bundles 用得比 libraries 还多,因为实际开发中很少单独引入一个依赖,都是一组一组地引入。
一个小技巧:如果你在升级依赖时不确定会不会出问题,可以在 toml 文件里保留旧版本号,用注释标记。比如:
[versions]
# spring-boot = "3.1.6" # 旧版本,回滚时取消注释
spring-boot = "3.2.0" # 新版本
这样回滚时只需要交换注释,不用去翻 Git 历史。嗯,这招是我在一次紧急回滚后总结出来的。
最后说一句,Version Catalog 不是银弹。如果你的项目只有两三个模块,依赖也不多,用不用 Catalog 差别不大。但一旦模块超过 5 个,或者你经历过「升级依赖漏改一个模块导致线上事故」的痛,你就会爱上这个工具。
公众号:蓝海资料掘金营,微信 deep3321