4 个互动演示:脚手架步骤 / 模块结构 / CI 流水线 / 最佳实践对比。
点击每张卡片标记完成,跟着指引走一遍。
gradle init --type basic --dsl kotlin
得到 gradlew 脚本和 wrapper 目录。
声明 includeBuild("build-logic") 和所有子模块 include(...)。
把所有版本号集中到 gradle/libs.versions.toml,告别裸版本号。
gradle.properties 里 daemon / parallel / caching / configuration-cache 全开。
编写 shop.java-conventions / shop.spring-conventions 等约定插件。
每个子模块只 plugins {} + dependencies {},不再写公共逻辑。
app 模块写一个 Spring Boot 启动类,./gradlew :app:bootRun 验证。
GitHub Actions / GitLab CI 跑 ./gradlew build,启用 Gradle 缓存。
带 ⭐ 的是关键文件,理解它们就理解了整个工程。
每个 build.gradle.kts 都很短(5-10 行);公共逻辑都在 build-logic 里;版本号都在 toml 里。
某个 feature 模块的 build.gradle.kts 写了 200 行;4 个模块各自写一遍 toolchain;版本号在多处硬编码。
点击下方按钮模拟一次 GitHub Actions 触发。
左红右绿:照右边写就对了。
// app/build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-web:3.2.0")
// feature-user/build.gradle.kts
implementation("org.springframework.boot:spring-boot-starter-web:3.1.5")
// → 不同模块版本不一致!
// gradle/libs.versions.toml [versions] spring-boot = "3.2.0" // build.gradle.kts implementation(libs.spring.boot.starter.web) // → 版本永远一致,IDE 自动补全
// build.gradle.kts (根)
subprojects {
apply(plugin = "java")
java { toolchain { ... } }
repositories { mavenCentral() }
dependencies {
"implementation"("...")
}
// ↑ 60 行 boilerplate
}
// build-logic/.../shop.java-conventions.gradle.kts
plugins { java }
java { toolchain { ... } }
dependencies { ... }
// 子模块只需:
plugins { id("shop.java-conventions") }
buildscript {
dependencies {
classpath("org.springframework.boot:...:3.2.0")
}
}
apply(plugin = "org.springframework.boot")
// → 老旧 + 不能享受 plugins {} DSL 的好处
plugins {
id("org.springframework.boot") version "3.2.0"
}
// 配合 settings.gradle.kts 的 pluginManagement,
// 还可以集中管理插件版本
# GitHub Actions - run: ./gradlew build # → 每次都重下所有依赖 # → 5 分钟变 25 分钟
- uses: gradle/actions/setup-gradle@v3
with:
cache-disabled: false
- run: ./gradlew build --scan
# → 自动缓存 Gradle Home + Build Cache
tasks.test {
useJUnitPlatform()
}
// 测试失败时控制台只看到一行
// 必须打开 build/reports/tests 才能查
tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
exceptionFormat = TestExceptionFormat.FULL
showStandardStreams = true
}
}