Appearance
第46课:Gradle
🎯 学习目标
- 理解 Gradle 相比 Maven 的优势(DSL 灵活、构建快)
- 掌握 build.gradle 结构(依赖、插件、Task)
- 掌握 Task 定义与生命周期
- 理解增量构建为什么快
- 能看懂常见 Gradle 配置
📖 一、概念讲解:Gradle vs Maven
1. Maven 的痛点
Maven 用 XML 配置,问题:
- XML 啰嗦:声明一个依赖要
<dependency><groupId>...<artifactId>...<version>...</dependency>五层标签。 - 不灵活:想写"如果某条件就加依赖"这种逻辑,XML 难表达。
- 构建慢:每次全量构建(不增量),大项目慢。
2. Gradle 怎么改进
- 用代码配置:Groovy/Kotlin DSL,写起来像代码(变量、条件、循环都能用)。
- 增量构建:只重新编译变化的部分(输入输出追踪),快很多。
- 构建缓存:跨项目/跨机器缓存任务结果。
- 依赖管理同 Maven:坐标、传递、冲突规则类似。
Maven(XML) Gradle(Groovy DSL)
<dependency> implementation 'org.springframework:spring-core:6.1.0'
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.1.0</version>
</dependency>一眼看出 Gradle 简洁。Android 项目默认用 Gradle。
📖 二、build.gradle 结构(Groovy DSL)
groovy
plugins {
id 'java' // Java 项目插件
id 'org.springframework.boot' version '3.2.0'
}
group = 'com.example' // groupId
version = '1.0.0' // version
repositories {
mavenCentral() // 仓库(中央)
maven { url 'https://maven.aliyun.com/repository/public' } // 阿里云镜像
}
dependencies {
// scope 直接是方法名,比 Maven 的 <scope> 简洁
implementation 'org.springframework:spring-core:6.1.0' // = compile
testImplementation 'junit:junit:4.13.2' // = test
runtimeOnly 'mysql:mysql-connector-java:8.0.33' // = runtime
compileOnly 'javax.servlet:javax.servlet-api:4.0.1' // = provided
}
// 自定义 Task
task hello {
doLast {
println 'Hello Gradle'
}
}依赖 scope 对应 Maven:
| Gradle | Maven | 说明 |
|---|---|---|
| implementation | compile | 编译+运行(默认) |
| testImplementation | test | 仅测试 |
| compileOnly | provided | 编译要,运行不打包 |
| runtimeOnly | runtime | 运行时才要 |
| api | compile(传递) | 暴露给依赖方 |
📖 三、Task(构建任务)
Gradle 一切是 Task(任务),Task 组成有向无环图(DAG)执行:
groovy
task copyDocs(type: Copy) {
from 'src/docs'
into 'build/docs'
}
task build {
dependsOn 'compileJava', 'test' // 依赖(先执行依赖任务)
doLast { println '构建完成' }
}- Task 依赖:
dependsOn声明依赖,被依赖的先执行。 - doFirst/doLast:在 Task 执行前/后加动作。
- 类型 Task:Copy/Jar/JavaCompile 等内置类型,复用。
生命周期
初始化(settings.gradle)→ 配置(执行 build.gradle 构建任务图)→ 执行(按依赖顺序跑 Task)配置阶段执行 build.gradle(构建任务图),执行阶段按 DAG 顺序跑 Task。
📖 四、增量构建为什么快
Gradle 追踪每个 Task 的输入和输出:
- 输入没变 + 输出存在 → 跳过该 Task(UP-TO-DATE)。
- 输入变了 → 只重跑该 Task 及依赖它的。
compileJava(输入:src/*.java,输出:build/classes)
→ 只改了 1 个 .java → 重编译该文件相关(增量)
Maven:每次全量编译所有构建缓存:跨项目/跨机器缓存 Task 结果(相同输入直接复用),CI 加速明显。
⚠️ 五、常见陷阱
陷阱1:DSL 语法错误
Groovy 语法灵活但易错(括号可省、引号规则)。Kotlin DSL 类型安全但啰嗦。初学用 Groovy。
陷阱2:implementation vs api
implementation 不传递依赖(下游看不到),api 传递。库开发用 implementation(减少暴露),但下游需用到则 api。
陷阱3:配置阶段执行重逻辑
build.gradle 在配置阶段执行,写重逻辑(如读文件、网络)会拖慢配置。重逻辑放 Task 执行阶段。
陷阱4:依赖版本冲突
Gradle 默认取最高版本(与 Maven 先声明不同)。可用 strict/force 强制版本。
陷阱5:Gradle 版本不兼容
Gradle 升级可能破坏构建(API 变化)。用 wrapper 锁定 Gradle 版本(./gradlew)。
🆚 六、Gradle vs Maven
| 特性 | Maven | Gradle |
|---|---|---|
| 配置 | XML(啰嗦) | Groovy/Kotlin DSL(灵活简洁) |
| 构建 | 全量 | 增量(快) |
| 缓存 | 弱 | 强(构建缓存) |
| 灵活性 | 低(约定强) | 高(代码逻辑) |
| 生态 | 成熟(Java 主流) | Android 主流,Java 增长 |
| 学习 | 简单 | DSL 稍陡 |
选择:Maven 约定强、生态成熟(Java 企业项目主流);Gradle 灵活快(Android、新项目、大项目追求构建速度)。
对 C 程序员:Gradle/Maven 都是 Java 构建工具,对应 C 的 Make,但用 DSL/XML 替代 Makefile,依赖管理更强(中央仓库自动下载)。Gradle 像"可编程的 Make"(能写逻辑)。
💡 七、最佳实践
- 用 wrapper 锁定版本:
./gradlew(不依赖机器装的 Gradle)。 - implementation 优先(减少传递),需暴露用 api。
- 增量构建:保证 Task 输入输出声明正确(能 UP-TO-DATE)。
- 构建缓存:CI 开启(跨机器复用)。
- 复杂逻辑放执行阶段,配置阶段轻量。
- Kotlin DSL(新项目):类型安全,IDE 补全好。
📝 练习预告
完成 练习/Ex46_Gradle.java 中的 6 道题:
- build.gradle 结构(对比 Maven)
- 依赖 scope(implementation/api/test)
- Task 定义与依赖
- 增量构建原理
- 依赖冲突(最高版本)
- 综合:用 Java 模拟 Task 依赖图执行
完成后对比 答案/Sol46.java,查看逐行讲解与多解法。
📖 八、Gradle Wrapper
生产项目必须使用 Wrapper。
文件:
text
gradlew
gradlew.bat
gradle/wrapper/gradle-wrapper.properties
gradle/wrapper/gradle-wrapper.jar作用:
text
锁定 Gradle 版本。
避免依赖开发者本机安装。
保证 CI 和本地使用同一构建工具版本。
升级 Gradle 可通过修改 wrapper 统一完成。常用命令:
bash
./gradlew build
./gradlew test
./gradlew clean build
./gradlew tasks
./gradlew dependenciesWindows 下:
powershell
.\gradlew.bat build📖 九、Kotlin DSL 与 Groovy DSL
Gradle 支持两种主流 DSL:
text
Groovy DSL:build.gradle。
Kotlin DSL:build.gradle.kts。对比:
| 特性 | Groovy DSL | Kotlin DSL |
|---|---|---|
| 写法 | 简洁灵活 | 类型安全 |
| IDE 补全 | 较弱 | 更强 |
| 学习成本 | 低 | 稍高 |
| 大型项目维护 | 容易写散 | 更稳 |
Groovy:
groovy
dependencies {
implementation 'org.springframework:spring-core:6.1.0'
}Kotlin:
kotlin
dependencies {
implementation("org.springframework:spring-core:6.1.0")
}新项目如果团队熟悉 Kotlin,优先考虑 Kotlin DSL;学习阶段用 Groovy 更容易上手。
📖 十、多项目构建
Gradle 多项目通常由 settings.gradle 声明模块:
groovy
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
rootProject.name = 'demo'
include 'domain', 'service', 'web'根 build.gradle 管理公共配置:
groovy
subprojects {
apply plugin: 'java'
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
}模块依赖:
groovy
dependencies {
implementation project(':domain')
}建议:
text
公共配置放 subprojects/allprojects 时要克制。
模块自己的依赖写在模块里。
不要让 common 模块无限膨胀。
明确模块依赖方向,避免循环依赖。📖 十一、自定义 Task 的输入输出
Gradle 增量构建依赖 Task 的输入输出。
示例:
groovy
tasks.register('generateVersionFile') {
def outputFile = layout.buildDirectory.file('generated/version.txt')
outputs.file(outputFile)
inputs.property('version', project.version)
doLast {
outputFile.get().asFile.text = project.version.toString()
}
}如果输入没变、输出存在,Gradle 可以跳过任务。
常见错误:
text
Task 读了文件但没声明 inputs。
Task 写了文件但没声明 outputs。
配置阶段就执行文件生成。
任务使用当前时间作为隐式输入,导致永远无法缓存。写自定义 Task 时要让 Gradle 知道“什么变了才需要重跑”。
🛠 十二、依赖冲突与版本锁定
查看依赖:
bash
./gradlew dependencies
./gradlew dependencyInsight --dependency jackson-databindGradle 默认倾向选择较高版本。可以用 constraints 约束:
groovy
dependencies {
constraints {
implementation('com.fasterxml.jackson.core:jackson-databind:2.16.0') {
because '统一 Jackson 版本,避免运行时冲突'
}
}
}也可以使用 platform/BOM:
groovy
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:3.2.0')
implementation 'org.springframework.boot:spring-boot-starter-web'
}排查冲突时重点看:
text
谁引入了这个依赖。
最终选择了哪个版本。
是否有强制版本。
是否和插件版本兼容。🚀 十三、构建性能优化
Gradle 快的前提是配置正确。
常见优化:
text
开启 Gradle Daemon。
使用增量构建。
开启 build cache。
避免配置阶段重逻辑。
减少 allprojects/subprojects 中的重配置。
使用 configuration cache。
拆分模块但避免过度拆分。gradle.properties 示例:
properties
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true诊断构建耗时:
bash
./gradlew build --scanBuild Scan 可以看到任务耗时、缓存命中、配置阶段耗时和依赖下载情况。
🛠 十四、Gradle 排查清单
构建失败时按下面顺序看:
text
是否使用 wrapper。
Gradle 版本是否支持当前 JDK。
插件版本是否兼容。
仓库是否可访问。
依赖是否解析失败。
任务是在配置阶段还是执行阶段报错。
是否开启了不兼容的 configuration cache。
多项目模块名是否写错。
本地缓存是否损坏。常用命令:
bash
./gradlew --version
./gradlew tasks
./gradlew projects
./gradlew dependencies
./gradlew clean build --stacktrace
./gradlew build --info--stacktrace 用于定位异常栈,--info 用于查看更多构建过程。
✅ 十五、掌握标准
学完本课后,应能做到:
text
能看懂 build.gradle 的 plugins、repositories、dependencies。
能解释 implementation、api、compileOnly、runtimeOnly。
能使用 Gradle Wrapper。
能定义简单 Task 并理解 dependsOn。
能解释 Gradle 初始化、配置、执行三个阶段。
能说明增量构建依赖输入输出声明。
能用 dependencies 和 dependencyInsight 排查依赖。
能判断 Maven 和 Gradle 的适用场景。Gradle 的优势来自“可编程 + 增量 + 缓存”。但灵活也意味着需要更强的规范,否则构建脚本会变成难维护的程序。
🎓 下一步
- 第47课:Git — 分支管理、合并策略、工作流