Gradle 版本演进全景:构建工具的进化密码
开篇:为什么你需要读懂 Gradle 的版本演进
如果你是一位 JVM 生态的开发者,几乎不可能绕开 Gradle。从 Android Studio 默认的构建工具,到 Spring Boot 项目的脚手架,再到大数据组件的源码编译,Gradle 已经成为现代构建领域的事实标准之一。然而很多开发者对 Gradle 的认知停留在"能跑就行"的阶段,遇到构建慢、依赖冲突、缓存失效等问题时只能反复重启 Daemon 或清空 .gradle 目录。
Gradle 从 5.0 到 9.0 经历了五次大版本跃迁,每一次都伴随着构建模型、依赖体系、性能范式的深刻变化。读懂这些变化,意味着你能用更少的代码描述更复杂的依赖关系,用更短的等待时间完成更大规模的构建,用更安全的方式引入第三方库。本文将从最基础的构建脚本讲起,按版本顺序梳理每个里程碑带来的用户视角变化,并提供可直接复制的代码用例。无论你是刚接触 Gradle 的新手,还是从 Maven 迁移过来的老兵,都能从中找到属于自己的升级路径。
Gradle 的核心心智模型
构建脚本的三阶段生命周期
Gradle 的每一次构建都经历三个阶段:初始化、配置、执行。初始化阶段确定参与构建的项目集合,Gradle 会执行 settings.gradle(.kts) 文件,决定哪些子项目被纳入构建。配置阶段会执行所有参与项目的 build.gradle(.kts) 文件,构建出完整的任务依赖图,但此时任务本身还没有真正执行。执行阶段则按照依赖顺序依次运行被请求的任务。
理解这三个阶段对调试构建问题至关重要。如果你在配置阶段执行了耗时操作(比如读取文件、发起网络请求),那么即使只是运行 gradle help 也会变慢。配置缓存(Configuration Cache)正是为了消除配置阶段的重复开销而生的特性,后文会详细展开。下面这段脚本展示了三阶段的可观察行为:
// settings.gradlerootProject.name='demo'include'app','core'// build.gradleprintln"配置阶段:项目名 =$project.name"tasks.register('hello'){println"配置阶段:注册 hello 任务"doLast{println"执行阶段:hello 任务运行"}}运行gradle hello时,你会先看到两行配置阶段的输出,再看到执行阶段的输出。如果再运行一次,配置阶段的输出依然会出现,因为传统模式下每次构建都会重新执行配置阶段。
任务、依赖与项目
任务是 Gradle 构建的最小执行单元,每个任务有输入、输出和动作。依赖则是任务之间的拓扑关系,Gradle 会根据依赖关系决定执行顺序。项目是组织代码的容器,一个 Gradle 构建可以包含多个项目,每个项目可以有自己的构建脚本和依赖声明。
任务之间通过dependsOn建立依赖,Gradle 会自动按拓扑顺序执行。任务的输入输出用于增量构建和缓存复用,如果输入没变,任务会被跳过。下面是一个典型的任务依赖示例:
tasks.register('compile'){doLast{println'编译源代码'}}tasks.register('test'){dependsOn'compile'doLast{println'运行测试'}}tasks.register('build'){dependsOn'test'doLast{println'打包发布'}}运行gradle build时,Gradle 会先执行 compile,再执行 test,最后执行 build。这种声明式的依赖描述是 Gradle 区别于 Make 等工具的核心特征之一。
插件与约定
插件是 Gradle 复用构建逻辑的主要机制。一个插件可以添加任务、配置依赖、扩展 DSL、注册扩展点。Gradle 内置了大量核心插件,如 java、application、maven-publish 等,社区还有成千上万的第三方插件。插件通过plugins {}块应用:
plugins{id'java'id'application'}application{mainClass='com.example.Main'}应用 java 插件后,Gradle 会自动添加 compileJava、test、jar、build 等任务,并约定 src/main/java、src/test/java 等目录结构。这种"约定优于配置"的思想借鉴自 Maven,但 Gradle 允许你通过 sourceSets 等扩展点自由覆盖约定。
基础功能使用
创建第一个 Gradle 项目
Gradle 提供了init任务用于生成项目骨架。在空目录执行gradle init,Gradle 会交互式询问项目类型(application、library 等)、构建脚本语言(Groovy 或 Kotlin DSL)、测试框架等,然后生成完整的脚手架。生成的项目包含 settings 文件、build 文件、Wrapper 脚本和示例源码。
Wrapper 是 Gradle 的精髓之一。它把 Gradle 发行版的下载和版本管理交给项目自身,确保团队成员和 CI 服务器使用完全相同的 Gradle 版本。生成 Wrapper 的命令是gradle wrapper --gradle-version=8.5,生成的 gradlew、gradlew.bat 和 gradle-wrapper.jar 应该提交到版本控制。下面是一个典型的项目结构:
my-app/ ├── settings.gradle ├── build.gradle ├── gradlew ├── gradlew.bat ├── gradle/wrapper/ │ └── gradle-wrapper.properties └── src/ ├── main/java/ └── test/java/声明依赖
依赖声明是构建脚本最常见的操作。Gradle 通过 configurations 把依赖分组到不同的用途(编译、运行、测试等),java 插件预定义了 implementation、api、testImplementation、runtimeOnly 等配置。下面是一个典型的依赖声明:
plugins{id'java'}repositories{mavenCentral()}dependencies{implementation'com.google.guava:guava:32.1.3-jre'implementation'org.slf4j:slf4j-api:2.0.9'runtimeOnly'ch.qos.logback:logback-classic:1.4.11'testImplementation'org.junit.jupiter:junit-jupiter:5.10.0'testRuntimeOnly'org.junit.platform:junit-platform-launcher'}implementation 和 api 的区别是 Gradle 6.0 之后才被广泛强调的:implementation 隐藏依赖的传递性,api 则暴露给下游消费者。合理使用 implementation 可以显著减少重新编译的范围。
配置仓库
仓库是依赖的来源。Gradle 支持本地目录、Maven 仓库、Ivy 仓库等多种类型。最常见的是 mavenCentral(),企业内部通常还有私有的 Nexus 或 Artifactory。从 Gradle 7.0 开始,推荐在 settings.gradle 中集中声明仓库,避免子项目各自为政:
// settings.gradledependencyResolutionManagement{repositories{mavenCentral()maven{url'https://repo.example.com/maven'credentials{username=findProperty('repoUser')password=findProperty('repoPass')}}}}这种集中式声明让仓库配置成为构建的"单一事实来源",子项目无法再添加额外仓库,从而保证依赖来源的一致性和可审计性。
自定义任务
除了插件提供的任务,开发者经常需要编写自定义任务。最简单的方式是用tasks.register注册一个闭包任务,更复杂的需求则可以定义 Task 类。下面是一个复制文件的自定义任务示例:
tasks.register('copyDocs',Copy){from'src/main/asciidoc'into'build/docs/html'rename'(.+)\\.adoc','$1.html'}tasks.register('release'){dependsOn'build','copyDocs'doLast{println"发布版本${project.version}"}}Copy 是 Gradle 内置的任务类型,它已经实现了增量构建和缓存支持。继承内置任务类型比自己从零实现要省心得多,这也是 Gradle 推荐的实践。
Gradle 5:Kotlin DSL 的正式登场
生产可用的 Kotlin DSL
Gradle 5.0 于 2018 年发布,最重要的里程碑是 Kotlin DSL 正式进入生产可用状态。在此之前,Kotlin DSL 还处于孵化阶段,API 不稳定,性能也不理想。5.0 之后,开发者可以在 .gradle.kts 文件中享受 IDE 的自动补全、类型检查、跳转定义等现代编辑器特性,这对大型项目和团队协作意义重大。
Kotlin DSL 的核心价值在于类型安全。Groovy DSL 灵活但容易写错配置名,编译期不会报错,只有运行时才发现。Kotlin DSL 则把大部分配置暴露为强类型 API,错误在编辑器里就能看到。下面是同一个依赖声明在两种 DSL 下的对比:
// build.gradle.kts (Kotlin DSL)plugins{java}dependencies{implementation("com.google.guava:guava:32.1.3-jre")testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")}// build.gradle (Groovy DSL)plugins{id'java'}dependencies{implementation'com.google.guava:guava:32.1.3-jre'testImplementation'org.junit.jupiter:junit-jupiter:5.10.0'}Kotlin DSL 的缺点是编译速度较慢,这个问题直到 Gradle 8.0 才得到显著改善。在 5.0 时代,大型项目的 Kotlin DSL 配置阶段可能比 Groovy DSL 慢一倍以上。
依赖版本对齐
Gradle 5.0 引入了依赖版本对齐机制,允许把一组相关依赖绑定到同一版本。这在 Jackson、Spring 等多模块库的场景下非常有用,避免出现 jackson-databind 是 2.15 而 jackson-core 是 2.14 的不一致情况。对齐通过 platform 或 BOM 导入实现:
dependencies{implementationplatform('com.fasterxml.jackson:jackson-bom:2.15.2')implementation'com.fasterxml.jackson.core:jackson-databind'implementation'com.fasterxml.jackson.core:jackson-core'implementation'com.fasterxml.jackson.core:jackson-annotations'}导入 BOM 后,所有 jackson-* 依赖都会自动使用 2.15.2 版本,无需逐个声明。这个特性在 6.0 之后进一步完善,与 Gradle 自有的 platform 概念融合。
任务超时
5.0 还引入了任务超时配置。某些任务(如网络下载、外部命令调用)可能因为环境问题卡死,导致整个构建挂起。设置超时后,任务超时会自动失败并释放资源:
tasks.register('downloadData'){timeout=Duration.ofMinutes(10)doLast{// 下载大文件}}超时默认是关闭的,需要显式设置。这个特性对 CI 流水线特别有用,避免一个卡死的任务阻塞整个流水线。
Gradle 6:依赖管理的全面革新
Gradle Module Metadata 默认启用
Gradle 6.0 于 2019 年 11 月发布,依赖管理是这次升级的重头戏。其中最底层也最重要的变化是 Gradle Module Metadata(GMM)默认启用。GMM 是 Gradle 自有的元数据格式,扩展了 Maven POM 的能力,支持变体、依赖约束、富版本、能力等高级特性。
GMM 文件名为.module,与 POM 并存。当使用 maven-publish 插件发布时,Gradle 会同时生成 POM 和 .module 文件。下游消费者如果是 Gradle 6+,会优先读取 .module 文件,从而获得变体感知等高级能力;如果是 Maven 或老版本 Gradle,则回退到 POM。这种向后兼容的设计让 GMM 可以平滑推广。
对普通用户来说,GMM 默认启用意味着你发布的库可以携带更丰富的元数据。例如,你可以声明某个依赖只在编译时需要、某个依赖只在运行时需要,消费者会自动选择正确的变体。下面是一个发布配置示例:
publishing{publications{mavenJava(MavenPublication){from components.java pom{name='My Library'description='A demo library'}}}}富版本约束
富版本约束是 6.0 引入的最常用特性之一。传统的版本声明只能写一个固定版本号或区间,富版本则允许表达"偏好"、“严格”、"拒绝"等多种语义。这在管理传递依赖时特别有用,可以避免下游意外升级到不兼容的版本。
dependencies{implementation('com.google.guava:guava'){version{strictly'[32.0,33.0['prefer'32.1.3-jre'reject'32.0.0-jre'}}}strictly 表示版本必须落在这个区间内,否则构建失败;prefer 表示在多个候选版本中优先选择;reject 表示明确排除某些版本。这些约束会被写入 GMM,对下游消费者生效。
平台与 BOM 集成
平台是 Gradle 自有的 BOM 概念,用于集中声明一组依赖的推荐版本。与 Maven BOM 不同,Gradle 平台可以包含富版本约束、能力声明等更丰富的信息。平台本身也是一个项目,通过 java-platform 插件发布:
// platform 项目的 build.gradleplugins{id'java-platform'}dependencies{constraints{api'com.google.guava:guava:32.1.3-jre'api'org.slf4j:slf4j-api:2.0.9'api'org.junit.jupiter:junit-jupiter:5.10.0'}}消费端通过platform(project(':my-platform'))或platform('com.example:my-platform:1.0')引入。平台与 BOM 可以互操作,Gradle 既能消费 Maven BOM,也能把自己的平台发布为 BOM 供 Maven 使用。
依赖约束
依赖约束用于影响传递依赖的版本,而不是直接声明依赖。这是解决"传递依赖版本过低"问题的正确方式,而不是直接把传递依赖提升为直接依赖。约束不会被发布为依赖,只会作为版本建议:
dependencies{constraints{implementation'commons-codec:commons-codec:1.16.0'}}如果某个传递依赖引入了 commons-codec,约束会强制使用 1.16.0 版本。如果没有传递依赖引入它,约束不会自动添加这个依赖。这种细粒度的控制是 6.0 之前难以做到的。
组件能力
组件能力用于检测和解决互斥依赖。最经典的例子是日志框架:slf4j 有多个实现(logback、log4j、slf4j-simple),它们互斥,只能选一个。6.0 之前,Gradle 会在 classpath 上同时出现多个实现,导致运行时警告。6.0 之后,插件可以声明能力,Gradle 会自动检测冲突并要求用户选择:
dependencies{implementation'org.slf4j:slf4j-api:2.0.9'implementation'ch.qos.logback:logback-classic:1.4.11'// 如果同时引入 log4j-slf4j-impl,Gradle 会报能力冲突}社区插件可以为自己的依赖声明能力,让 Gradle 帮助检测冲突。这是构建可观测性和正确性的重要进步。
特性变体与测试夹具
特性变体允许一个库暴露多个可选功能,每个功能有自己的依赖。最常见的应用是测试夹具:一个库可以发布主产物和测试夹具产物,下游项目的测试代码可以依赖测试夹具,而主代码不会引入测试夹具的依赖。
plugins{id'java-library'id'java-test-fixtures'}dependencies{testFixturesImplementation'org.junit.jupiter:junit-jupiter:5.10.0'}应用 java-test-fixtures 插件后,src/testFixtures/java 目录下的代码会被编译为测试夹具产物并发布。下游项目可以这样消费:
dependencies{implementation'com.example:my-lib:1.0'testImplementation(testFixtures('com.example:my-lib:1.0'))}这种模式比传统的"把测试工具类复制到每个项目"或"发布单独的 -test.jar"要优雅得多。
更快的增量编译
6.0 对增量 Java 编译做了改进,能够识别"实现细节"类,避免不必要的重编译。例如,类 A 被类 B 使用,类 B 被类 C 使用,当 A 改变时,传统增量编译会重编译 A、B、C 三个类。6.0 之后,如果 B 只是把 A 当作实现细节(比如只在方法内部使用 A,而不在签名中暴露 A),则 C 不需要重编译。
对于深层依赖链的大型项目,这个优化可以显著减少增量编译的范围。底层实现基于对字节码的分析,识别哪些类是 API 暴露的、哪些是内部实现。这个特性是自动启用的,无需配置。
Gradle 7:性能与安全的双重跃迁
文件系统监视默认启用
Gradle 7.0 于 2021 年 5 月发布,最直观的变化是增量构建更快了。这得益于文件系统监视(File System Watching)默认启用。在此之前,Gradle 每次构建都要扫描所有输入文件计算快照,对于大型项目这可能耗时数秒。启用文件系统监视后,Gradle 会维护一个后台进程监听文件变化,下次构建时直接查询变化记录,跳过全量扫描。
文件系统监视在 Linux、macOS、Windows 上都可用,7.0 还增加了对 Apple Silicon(M1)的原生支持。用户无需任何配置就能享受这个加速,这是 7.0 最受欢迎的改进之一。底层实现使用了操作系统原生的文件监听 API(如 Linux 的 inotify、macOS 的 FSEvents)。
如果遇到文件系统监视的兼容性问题,可以通过org.gradle.vfs.watch=false关闭。但绝大多数情况下,这个特性是稳定且有益的。
配置缓存(实验性)
配置缓存是 7.0 引入的最具前瞻性的特性。它缓存配置阶段的结果,下次构建时如果构建脚本和输入没变,直接复用缓存的任务图,跳过整个配置阶段。对于配置阶段耗时较长的大型项目,这可以节省数秒甚至数十秒。
7.0 时代配置缓存还是实验性的,需要显式启用:
# gradle.properties org.gradle.configuration-cache=true启用后,第一次构建会正常执行配置阶段并写入缓存,第二次构建如果脚本没变则直接复用缓存。配置缓存对构建脚本和插件有严格要求:不能在任务执行阶段访问 Project 对象、不能使用外部状态等。不兼容的插件会导致缓存失效或构建失败。
配置缓存的底层原理是把任务图序列化到磁盘,下次构建反序列化后直接执行任务。这要求任务闭包不捕获不可序列化的状态,这也是为什么很多老插件需要改造才能兼容。这个特性在 8.0 和 9.0 逐步成熟,最终在 9.0 成为首选执行模式。
版本目录
版本目录是 7.0 引入的依赖集中管理方案,解决了多项目构建中依赖版本散落各处的问题。它用一个 TOML 文件集中声明所有依赖的坐标和版本,子项目通过类型安全的方式引用:
# gradle/libs.versions.toml [versions] guava = "32.1.3-jre" junit = "5.10.0" [libraries] guava = { module = "com.google.guava:guava", version.ref = "guava" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } [plugins] java-library = { id = "java-library", version = "none" }// build.gradledependencies{implementation libs.guava testImplementation libs.junit.jupiter}Kotlin DSL 还能享受 IDE 的自动补全和类型检查。版本目录在 7.0 是实验性的,7.4 之后稳定,8.0 之后还支持在 plugins 块中使用别名。这是现代 Gradle 项目管理依赖的推荐方式。
Java 工具链
工具链让构建脚本声明项目需要的 JDK 版本,Gradle 会自动从本地查找或从远程下载匹配的 JDK。这解决了"开发机器 JDK 版本不一致"的痛点,也允许构建用 Java 17 编译,同时用 Java 11 运行测试。
java{toolchain{languageVersion=JavaLanguageVersion.of(17)}}Gradle 会检测本地的 JDK 安装(包括 SDKMAN!、jabba、asdf-vm 等包管理器安装的),如果找不到匹配版本,会从 AdoptOpenJDK 下载。工具链在 7.0 引入,后续版本不断增强,8.0 之后还支持指定 vendor、自动配置 Daemon JVM 等。
依赖验证
依赖验证是 7.0 的安全特性,允许构建声明依赖的校验和和签名,Gradle 会在解析依赖时验证。这可以防止依赖被篡改或供应链攻击。启用方式是生成验证元数据文件:
gradle --write-verification-metadata sha256help这会生成 gradle/verification-metadata.xml 文件,记录所有依赖的 SHA-256 校验和。之后任何依赖的变更都会被检测,未签名的依赖会被拒绝。对于安全敏感的项目(如金融、医疗),这是必备特性。
单一依赖锁文件
依赖锁定用于固定传递依赖的版本,确保可重现构建。7.0 之前,每个配置一个锁文件,管理起来很麻烦。7.0 改为单一锁文件gradle/dependencies.lock,所有配置的锁定状态集中管理。启用方式:
# gradle.properties dependencyLocking.lockAllConfigurations()gradle dependencies --write-locks锁文件应该提交到版本控制,团队成员和 CI 都会使用相同的传递依赖版本。这与版本目录互补:版本目录管理直接依赖,锁文件管理传递依赖。
Gradle 8:开发者体验的精雕细琢
Kotlin DSL 编译提速
Gradle 8.0 于 2023 年 2 月发布,Kotlin DSL 用户最直接的受益是编译速度提升约 20%。这得益于对 plugins {} 块的解释器优化:8.0 之前,plugins 块需要调用 Kotlin 编译器解析,8.0 引入了一个轻量级解释器,能直接处理标准格式的 plugins 块,跳过编译器调用。
要享受这个加速,plugins 块必须使用受约束的语法:
plugins{javaid("com.example.plugin")version"1.0"kotlin("jvm")version"1.9.0"}如果使用了版本目录别名(如alias(libs.plugins.example))或类型安全访问器,解释器会回退到编译器模式,无法享受加速。这种限制在后续版本逐步放宽。
8.0 还把 Kotlin DSL 的 API 级别从 1.4 提升到 1.8,开发者可以在构建脚本中使用 Kotlin 1.8 的所有语言特性(如 sealed interface、context receiver 等)。同时,Kotlin DSL 脚本的编译目标从 Java 8 字节码改为运行 Gradle 的 JVM 版本,如果团队用 Java 17 跑 Gradle,构建脚本就能用 Java 17 的库。
配置缓存的成熟
8.0 时代配置缓存从实验性走向稳定,最显著的改进是首次构建也能并行执行任务。在此之前,配置缓存只在缓存命中时启用并行,首次构建(缓存未命中)仍然串行执行配置阶段。8.0 之后,即使缓存未命中,任务也会在配置阶段完成后立即并行执行。
# gradle.properties org.gradle.configuration-cache=true org.gradle.parallel=true8.1 引入了配置缓存加密,用机器特定的密钥加密缓存文件,防止敏感数据(如仓库凭证)泄露。8.10 引入了字符串去重,大幅减小缓存文件体积,AndroidX 团队报告缓存体积减少 3.75 倍。8.11 引入了并行加载和存储,进一步缩短缓存读写时间。
这些渐进式改进让配置缓存在 8.x 时代逐步成为可生产使用的特性,为 9.0 的"首选执行模式"奠定了基础。
buildSrc 的进化
buildSrc 是 Gradle 组织构建逻辑的特殊项目,它的代码会被自动编译并加入构建脚本的 classpath。8.0 对 buildSrc 做了多项改进,让它更接近普通 included build 的行为。
最实用的改进是 buildSrc 的任务可以直接从命令行运行:
gradle buildSrc:build gradle buildSrc:test之前要运行 buildSrc 的测试很麻烦,现在和普通项目一样。8.0 还允许 buildSrc 包含其他构建(在 buildSrc/settings.gradle 中声明 includeBuild),让构建逻辑的模块化更灵活。另外,buildSrc 不再自动运行 test 任务,只有真正需要 buildSrc 输出时才编译,减少了不必要的开销。
缓存清理与保留
8.0 之前,Gradle 用户主目录(~/.gradle)会无限增长,缓存文件可能占用数十 GB 空间。8.0 引入了可配置的缓存清理和保留策略:
# gradle.properties org.gradle.cache.cleanup=true// settings.gradlecacheCleanup{policy=CleanupPolicy.ON_BUILD_COMPLETION every=Duration.ofDays(7)retention{files(DownloadedFiles.ALL){maxAge=Duration.ofDays(30)}}}默认保留 7 天未使用的缓存,超过则清理。这个特性对开发机器和 CI 都很友好,避免磁盘被缓存撑爆。
Java Toolchains 改进
8.0 对工具链做了多项增强。最实用的是工具链供应商匹配,可以指定只使用特定厂商的 JDK:
java{toolchain{languageVersion=JavaLanguageVersion.of(17)vendor=JvmVendorSpec.ADOPTIUM}}8.8 引入了 Daemon JVM 工具链,允许 Gradle Daemon 运行在与 CLI 不同的 JVM 上。8.13 进一步支持 Daemon JVM 自动配置,本地找不到匹配 JDK 时自动下载。8.14 支持 GraalVM 工具链,能选择支持 Native Image 的 JDK。
这些改进让 Gradle 在多 JDK 环境下的行为更可预测,对需要在不同 Java 版本间切换的项目特别有用。
编译器守护进程保活
8.3 引入了 Java 编译器守护进程保活,在 Linux 和 macOS 上把编译时间缩短最多 30%。之前每次编译任务都会启动新的 javac 进程,保活后编译器进程在构建之间保持存活,避免重复启动开销。8.4 把这个特性扩展到 Windows。
这个特性是自动启用的,无需配置。对于以 Java 编译为主的大型项目(如 Spring 框架本身),效果立竿见影。
Gradle 9:面向未来的构建范式
配置缓存成为首选执行模式
Gradle 9.0.0 于 2025 年 7 月发布,最重要的变化是配置缓存成为首选执行模式。虽然还没有默认启用,但 Gradle 会在构建结束时主动建议启用,并在 gradle init 生成的新项目中默认开启。9.0 的目标是让配置缓存在 10.0 成为默认且唯一的执行模式。
9.0 引入了优雅降级机制:当遇到不兼容配置缓存的插件或特性时,Gradle 会自动回退到传统模式,而不是直接失败。这包括 Maven Publish、Ivy Publish 等核心插件的部分功能,以及 Eclipse、IDEA 等 IDE 插件。降级原因会写入配置缓存报告,方便排查。
# gradle.properties org.gradle.configuration-cache=true # 如果不想看到启用提示,可以显式关闭 # org.gradle.configuration-cache=false9.0 还清理了大量与配置缓存不兼容的废弃 API,强制插件作者迁移到安全替代方案。短期内这可能导致一些老插件无法使用,但长期看是必要的阵痛。
Java 17 与 Kotlin 2 / Groovy 4
9.0 要求 Java 17 或更高版本来运行 Gradle Daemon。这是一个重大变化,许多仍在用 Java 8 或 11 的项目需要先升级运行环境。需要注意的是,这个要求只针对运行 Gradle 本身,编译和测试代码仍然可以用工具链指定更老的 Java 版本。
java{toolchain{languageVersion=JavaLanguageVersion.of(8)// 编译目标可以是 Java 8}}9.0 嵌入了 Kotlin 2.2.x 运行时,使用 Kotlin 语言版本 2.2。这意味着构建脚本和插件可以用上 K2 编译器和 Kotlin 2 的新特性。同时 Groovy 升级到 4.0,带来新的语言特性和性能改进。这两个升级主要影响插件作者,普通用户的构建脚本通常不需要改动。
语义化版本控制
从 9.0 开始,Gradle 采用语义化版本控制(SemVer),版本号格式为 MAJOR.MINOR.PATCH。之前的小版本号省略 PATCH 段(如 8.5 而不是 8.5.0),9.0 之后所有新版本都遵循 SemVer。这个变化对用户来说意味着版本号更规范,自动化脚本解析版本号更可靠。
# Gradle 8.x 写法./gradlew wrapper --gradle-version=8.5# Gradle 9.x 写法./gradlew wrapper --gradle-version=9.0.0另外,标记为 @Incubating 的 API 不再被视为公共 API 的一部分,可能在次要版本中变更。这给了 Gradle 团队更多迭代空间,也提醒用户谨慎依赖孵化期 API。
Kotlin DSL 编译避免
9.0 对 Kotlin DSL 的脚本编译避免做了重大改进。之前 Gradle 用内部机制检测构建逻辑的 ABI 变化,对内联函数等场景处理不佳。9.0 改用 Kotlin 内置的 ABI 指纹,检测精度大幅提升。
对于 Gradle 自身的构建,非 ABI 变更的构建逻辑修改可以让配置时间减少最多 60%。这意味着你修改一个注释或方法体,不会触发所有 Kotlin DSL 脚本的重新编译。这个改进对大型 Kotlin DSL 项目特别友好。
可重现的归档输出
9.0 让归档任务(jar、zip 等)的输出默认可重现。之前归档文件中条目的顺序可能因文件系统而异,导致同样的输入产生不同的输出,影响构建缓存命中。9.0 之后,归档顺序固定(按字典序),时间戳固定,确保可重现构建。
tasks.named('jar'){reproducibleFileOrder=truepreserveFileTimestamps=false}这个特性对发布到 Maven 仓库的库特别重要,可重现的产物让下游的构建缓存更有效。
升级建议与实践
版本选择策略
对于新项目,直接使用最新的 Gradle 9.x 是合理选择,能享受配置缓存、Kotlin 2、更好的性能等所有现代特性。新项目没有历史包袱,gradle init 生成的脚手架已经默认启用配置缓存。
对于存量项目,建议按版本逐步升级,不要一次跨越多个大版本。每次升级前先运行gradle help --scan生成构建扫描报告,查看废弃 API 的使用情况。升级路径通常是 6.x → 7.x → 8.x → 9.x,每个大版本先升级到该系列的最后一个次要版本(如 7.6、8.14),再升级到下一个大版本。
配置缓存迁移
配置缓存是 9.0 的核心,建议尽早迁移。迁移步骤:先在 gradle.properties 中启用org.gradle.configuration-cache.problems=warn,运行常用任务查看不兼容警告;逐个修复不兼容的插件和自定义任务;最后把 problems 改为 fail,确保严格兼容。
常见的不兼容问题包括:在任务执行阶段访问 Project 对象、使用外部全局状态、任务闭包捕获不可序列化的对象。修复方式通常是把这些状态作为任务输入声明,或改用 Provider 和 Property API。
依赖管理现代化
把传统的 ext 版本声明或 dependencies.gradle 文件迁移到版本目录。版本目录是 7.0 引入、8.0 稳定的特性,是当前推荐的依赖管理方式。迁移后所有依赖坐标集中在 libs.versions.toml 文件中,IDE 支持良好,团队协作更顺畅。
同时检查 implementation 和 api 的使用是否合理。把不必要的 api 改为 implementation,可以减少传递依赖的暴露,加快下游编译速度。这个区分从 6.0 开始被强调,是依赖卫生的重要实践。
工具链与 Wrapper
为所有项目配置 Java 工具链,避免依赖开发机器的 JDK 版本。工具链从 7.0 引入,9.0 已经非常成熟,支持自动下载、vendor 匹配、GraalVM 等高级特性。
始终使用 Wrapper 提交 Gradle 版本到版本控制,团队成员和 CI 都用相同的版本。升级时通过./gradlew wrapper --gradle-version=x.y.z命令更新,不要手动修改 gradle-wrapper.properties。9.0 之后版本号遵循 SemVer,记得写完整的 x.y.z 形式。
构建工具的演进从未停止,Gradle 10 已经在酝酿中,配置缓存默认启用、更激进的并行执行、更智能的增量检测都在路线图上。理解每个版本的演进逻辑,不仅能让你更好地使用当前版本,也能在未来升级时快速适应。构建是一门手艺,而掌握工具的演进史,是成为优秀构建工程师的必经之路。
