Skip to content

第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

GradleMaven说明
implementationcompile编译+运行(默认)
testImplementationtest仅测试
compileOnlyprovided编译要,运行不打包
runtimeOnlyruntime运行时才要
apicompile(传递)暴露给依赖方

📖 三、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

特性MavenGradle
配置XML(啰嗦)Groovy/Kotlin DSL(灵活简洁)
构建全量增量(快)
缓存强(构建缓存)
灵活性低(约定强)高(代码逻辑)
生态成熟(Java 主流)Android 主流,Java 增长
学习简单DSL 稍陡

选择:Maven 约定强、生态成熟(Java 企业项目主流);Gradle 灵活快(Android、新项目、大项目追求构建速度)。

对 C 程序员:Gradle/Maven 都是 Java 构建工具,对应 C 的 Make,但用 DSL/XML 替代 Makefile,依赖管理更强(中央仓库自动下载)。Gradle 像"可编程的 Make"(能写逻辑)。


💡 七、最佳实践

  1. 用 wrapper 锁定版本./gradlew(不依赖机器装的 Gradle)。
  2. implementation 优先(减少传递),需暴露用 api。
  3. 增量构建:保证 Task 输入输出声明正确(能 UP-TO-DATE)。
  4. 构建缓存:CI 开启(跨机器复用)。
  5. 复杂逻辑放执行阶段,配置阶段轻量。
  6. Kotlin DSL(新项目):类型安全,IDE 补全好。

📝 练习预告

完成 练习/Ex46_Gradle.java 中的 6 道题:

  1. build.gradle 结构(对比 Maven)
  2. 依赖 scope(implementation/api/test)
  3. Task 定义与依赖
  4. 增量构建原理
  5. 依赖冲突(最高版本)
  6. 综合:用 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 dependencies

Windows 下:

powershell
.\gradlew.bat build

📖 九、Kotlin DSL 与 Groovy DSL

Gradle 支持两种主流 DSL:

text
Groovy DSL:build.gradle。
Kotlin DSL:build.gradle.kts。

对比:

特性Groovy DSLKotlin 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-databind

Gradle 默认倾向选择较高版本。可以用 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 --scan

Build 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 — 分支管理、合并策略、工作流