Skip to content

第 3 章 核心概念:Project / Task / Plugin / 生命周期

学习目标:搞懂 Gradle 的"四块基石"—— Project(项目)、Task(任务)、Plugin(插件)、生命周期(Build Lifecycle);理解为什么 dependencies { ... } 在配置阶段执行,为什么 doLast { ... } 在执行阶段才跑;能画出 gradle build 的 Task 依赖图。

这是整本笔记最关键的一章。看懂这章,看任何 build.gradle.kts 都不会再发懵;看不懂这章,后面所有内容都会一头雾水。


3.1 用一张图概括 Gradle 的世界

   ┌─────────────────────────────────────────────────────────────────┐
   │                         Build (整个构建)                           │
   │                                                                   │
   │   ┌─────────────────────┐      ┌─────────────────────┐           │
   │   │   Project (一个项目) │      │   Project (另一个)   │   ...     │
   │   │                     │      │                     │           │
   │   │ ┌─────┐ ┌─────┐    │      │  ┌─────┐ ┌─────┐   │           │
   │   │ │Task │ │Task │ ... │      │  │Task │ │Task │   │           │
   │   │ └─────┘ └─────┘    │      │  └─────┘ └─────┘   │           │
   │   │                     │      │                     │           │
   │   │ ★ Plugin 给 Project   │      │ ★ Plugin 给 Project │           │
   │   │   注入一组 Task / 配置 │      │   注入一组 Task / 配置│           │
   │   └─────────────────────┘      └─────────────────────┘           │
   │                                                                   │
   └─────────────────────────────────────────────────────────────────┘
   ★ 生命周期 (Build Lifecycle):Initialization → Configuration → Execution

记住这张图:整个 Gradle 就在干一件事 —— 通过 Plugin 给 Project 装上一堆 Task,再按顺序执行用户指定的 Task


3.2 Project:构建的中心

3.2.1 什么是 Project?

一个 Project = 一个可独立构建的产物。可以是:

  • 一个 jar 包
  • 一个 war 包
  • 一个 APK
  • 一个 zip 发行包
  • 啥都不产出(只用作 root 协调子模块)

每个 build.gradle.kts 文件背后对应一个 Project 对象。所有你在脚本里写的 dependencies { }tasks.register { },本质上都是调用 Project 对象的方法

kotlin
// build.gradle.kts —— 看起来像配置文件
plugins { java }
group = "com.example"
version = "1.0.0"
dependencies { implementation("...") }

// 实际上 Gradle 把它解释为:
// (this 隐式指向 project 对象)
project.plugins.apply("java")
project.group = "com.example"
project.version = "1.0.0"
project.dependencies.implementation("...")

📌 生活化类比:把 Project 想成"一家工厂"。group/version 是工厂名片;plugins 是工厂里安装的"流水线"(如装个 Java 编译流水线);tasks 是流水线上的"工序"。

3.2.2 多 Project 的层级

实际工程里很少只有 1 个 Project,更多是根 Project + 多个子 Project

my-project/                       ← 根 Project(root)
├── settings.gradle.kts           ← 声明哪些子 Project
├── build.gradle.kts              ← 根 Project 的脚本(可选)

├── app/                          ← 子 Project ":app"
│   └── build.gradle.kts

├── lib-core/                     ← 子 Project ":lib-core"
│   └── build.gradle.kts

└── feature-billing/              ← 子 Project ":feature-billing"
    └── build.gradle.kts

settings.gradle.kts

kotlin
rootProject.name = "my-project"
include("app", "lib-core", "feature-billing")

每个子 Project 都是独立的 Project 对象,但共享同一个 Gradle 进程。这是多模块项目能极速并行构建的基础。

3.2.3 引用其他 Project

子 Project 之间想互相依赖:

kotlin
// app/build.gradle.kts
dependencies {
    implementation(project(":lib-core"))          // 用 :path 引用
    implementation(project(":feature-billing"))
}

根 Project 可以一次性给所有子 Project 配公共配置:

kotlin
// 根 build.gradle.kts
subprojects {
    apply(plugin = "java")
    java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
    repositories { mavenCentral() }
}

⚠️ 现代最佳实践是用 Convention Plugin 替代 subprojects {}。第 8 章会讲。


3.3 Task:构建的最小单元

3.3.1 什么是 Task?

Task = 一个具体的"动作单元"。比如:

  • compileJava —— 编译 Java
  • test —— 跑测试
  • jar —— 打 jar 包
  • clean —— 清空 build 目录

每个 Task 有:

  • 一个名字(在 Project 里唯一)
  • 零个或多个依赖(必须先跑 X,才能跑我)
  • 零个或多个 actions(doFirst / doLast 里的代码)
  • input / output 声明(决定增量构建是否生效)

3.3.2 注册一个 Task

kotlin
tasks.register("hello") {
    group = "intro"                           // 在 ./gradlew tasks 列表里的分组
    description = "打印 hello"                  // 描述
    doLast {                                   // 执行阶段干的事
        println("Hello from Gradle!")
    }
}

跑:

bash
$ ./gradlew hello
> Task :hello
Hello from Gradle!

BUILD SUCCESSFUL in 600ms

3.3.3 doFirst vs doLast:动作链表

关键概念:Task 不是"一段代码",而是"一个动作链表"。

kotlin
tasks.register("multi") {
    doFirst { println("1️⃣ doFirst-A") }      // 加到链表头
    doFirst { println("2️⃣ doFirst-B") }      // 又加到链表头
    doLast  { println("3️⃣ doLast-X")  }      // 加到链表尾
    doLast  { println("4️⃣ doLast-Y")  }      // 又加到链表尾
}

执行顺序:

执行队列:
  [doFirst-B (后加,挤到前)]
  [doFirst-A]
  [doLast-X]
  [doLast-Y (后加,挤到尾)]

输出:
  2️⃣ doFirst-B
  1️⃣ doFirst-A
  3️⃣ doLast-X
  4️⃣ doLast-Y

📌 生活化类比:把 Task 想成"准备出门",doFirst 是"穿衣服前先做的事"(最近加的最先做:先擦防晒霜 → 再涂润肤露 → 再穿衣服),doLast 是"穿完衣服后做的事"(最早加的最先做:先关灯 → 再锁门)。

3.3.4 Task 之间的依赖

3 种声明方式:

kotlin
// 方式 1:dependsOn(强依赖:B 必须在 A 之后跑)
tasks.register("compile") { doLast { println("compile") } }
tasks.register("package") {
    dependsOn("compile")
    doLast { println("package") }
}
// 跑 ./gradlew package 会先跑 compile

// 方式 2:mustRunAfter(弱依赖:如果两个都要跑,B 必须在 A 之后;但单独跑 B 不会触发 A)
tasks.register("smokeTest") {
    mustRunAfter("test")
    doLast { println("smoke") }
}

// 方式 3:finalizedBy(A 跑完后,必须跑 B —— 即使 A 失败)
tasks.register("upload") {
    doLast { println("upload") }
    finalizedBy("cleanup")
}
tasks.register("cleanup") {
    doLast { println("cleanup") }
}

3.3.5 Task 的依赖图(DAG)

Gradle 在配置阶段把所有 Task 的依赖关系组合成一个 有向无环图(DAG)。跑某个 Task 时,从该 Task 反向追踪所有依赖,按拓扑序执行。

举例:跑 ./gradlew build,DAG 大致这样:

                     ┌──────────────────┐
                     │     :build       │
                     └────────┬─────────┘

              ┌───────────────┴───────────────┐
              ▼                               ▼
       ┌──────────────┐              ┌──────────────┐
       │  :assemble   │              │   :check     │
       └──────┬───────┘              └──────┬───────┘
              │                              │
              ▼                              ▼
       ┌──────────────┐              ┌──────────────┐
       │    :jar      │              │    :test     │
       └──────┬───────┘              └──────┬───────┘
              │                              │
              ▼                              ▼
       ┌──────────────┐              ┌──────────────┐
       │   :classes   │              │ :testClasses │
       └──────┬───────┘              └──────┬───────┘
              │                              │
       ┌──────┴────────┐                     │
       ▼               ▼                     ▼
 ┌──────────┐  ┌──────────────┐  ┌─────────────────┐
 │:compile  │  │ :process     │  │ :compileTest    │
 │  Java    │  │  Resources   │  │   Java          │
 └──────────┘  └──────────────┘  └─────────────────┘

Gradle 会自动并行执行没有依赖关系的 Task(如 compileJava 和 processResources 可以同时跑)。

3.3.6 看 DAG 的命令

bash
# 列出所有 task
$ ./gradlew tasks

# 看某个 task 的依赖(dry run,不实际执行)
$ ./gradlew build --dry-run
:compileJava SKIPPED
:processResources SKIPPED
:classes SKIPPED
:jar SKIPPED
:assemble SKIPPED
:compileTestJava SKIPPED
:processTestResources SKIPPED
:testClasses SKIPPED
:test SKIPPED
:check SKIPPED
:build SKIPPED

# 用 task tree 插件(推荐)
$ ./gradlew build taskTree

3.4 Plugin:把一组功能"打包"装进 Project

3.4.1 为什么要 Plugin?

如果没有 Plugin,每个 Java 项目都要自己写:

  • compileJava task
  • jar task
  • test task
  • 设置 src/main/java、src/test/java 这些目录约定
  • ...

这些是 每个 Java 项目都要的公共逻辑。Gradle 把它们封装成 java 插件 —— 你只要 plugins { java },立刻拥有所有这些 Task 和约定。

3.4.2 应用 Plugin 的两种方式

kotlin
// ✅ 推荐:plugins DSL(类型安全、版本声明、自动加载到 classpath)
plugins {
    java
    id("org.springframework.boot") version "3.2.0"
    kotlin("jvm") version "1.9.20"
}

// ❌ 老语法(apply)—— 看老项目可能见到,新项目不要用
apply(plugin = "java")
buildscript {
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:3.2.0")
    }
}
apply(plugin = "org.springframework.boot")

3.4.3 Plugin 的三大类型

类型来源应用方式
Core Plugin(核心)Gradle 自带plugins { java }plugins { application }plugins { jvm-test-suite }
Community Plugin(社区)Plugin Portalplugins { id("org.springframework.boot") version "3.2.0" }
Local / Custom Plugin(自定义)项目内 buildSrc / build-logicplugins { id("my.convention") }

3.4.4 一个 Plugin 给你装了什么

java 插件为例:

   plugins { java } 实际上自动给你做了:

   ① 注册 6 个 Task:
      - compileJava
      - processResources
      - classes (聚合)
      - compileTestJava
      - processTestResources
      - testClasses (聚合)
      - jar
      - test
      - check (聚合)
      - assemble (聚合)
      - build (聚合)
      - clean

   ② 注册 4 个 Configuration(依赖配置):
      - implementation
      - api (需要 java-library 插件)
      - compileOnly
      - runtimeOnly
      - testImplementation
      - ...

   ③ 设置 SourceSet 约定:
      - main → src/main/java、src/main/resources
      - test → src/test/java、src/test/resources

   ④ 设置 build/ 输出目录约定

📌 生活化类比:Plugin 像是"外卖软件里的一道套餐"。你点"java 套餐",立刻把"compile / test / jar / clean"这一组餐都端上来了,不用一道一道点。

3.4.5 常用插件速查

插件 ID用途
javaJava 编译
java-libraryJava 库(含 api 配置)
application可执行 Java 应用(带 run 任务)
kotlin("jvm")Kotlin JVM
org.springframework.bootSpring Boot
com.android.applicationAndroid 应用
com.android.libraryAndroid 库
maven-publish发布到 Maven 仓库
signing给产物签名
jacoco代码覆盖率
idea / eclipse生成 IDE 元数据(很少用)

3.5 生命周期(Build Lifecycle):Gradle 跑构建的 3 个阶段

这是整个第 3 章最关键的部分。理解 3 个阶段,是看懂任何 build.gradle.kts 的钥匙。

3.5.1 三个阶段总览

                ★ Gradle Build Lifecycle ★

  ┌─────────────────────────┐
  │  ① Initialization 阶段   │   读 settings.gradle.kts
  │     "我要构建几个项目?"   │   决定有哪些 Project
  └────────────┬────────────┘


  ┌─────────────────────────┐
  │  ② Configuration 阶段    │   执行所有 Project 的 build.gradle.kts
  │     "把所有 Task 都创建好" │   构造 Task 对象图(DAG)
  └────────────┬────────────┘   ⚠️ 不执行 Task 的实际动作!


  ┌─────────────────────────┐
  │  ③ Execution 阶段        │   按用户指定的 Task 反向追踪 DAG
  │     "按图跑 Task"         │   依次执行需要的 Task 的动作
  └─────────────────────────┘

3.5.2 一段代码看尽 3 个阶段

kotlin
println("[1] 这行在哪个阶段执行?")    // ← Configuration 阶段(脚本本身)

tasks.register("hello") {
    println("[2] 这行在哪个阶段执行?")  // ← Configuration 阶段(注册 Task 时)

    doFirst {
        println("[3] 这行在哪个阶段执行?")  // ← Execution 阶段
    }
    doLast {
        println("[4] 这行在哪个阶段执行?")  // ← Execution 阶段
    }
}

println("[5] 这行在哪个阶段执行?")    // ← Configuration 阶段

./gradlew hello

[1] 这行在哪个阶段执行?      ← Configuration(顺序执行脚本)
[2] 这行在哪个阶段执行?      ← Configuration(register 闭包是配置代码)
[5] 这行在哪个阶段执行?      ← Configuration

> Task :hello
[3] 这行在哪个阶段执行?      ← Execution(doFirst 在执行阶段才跑)
[4] 这行在哪个阶段执行?      ← Execution

BUILD SUCCESSFUL

./gradlew tasks没跑 hello):

[1] 这行在哪个阶段执行?      ← Configuration 还是会跑!
[2] 这行在哪个阶段执行?      ← Configuration 还是会跑!
[5] 这行在哪个阶段执行?      ← Configuration 还是会跑!

# [3] [4] 不会跑,因为 hello task 没被执行

3.5.3 关键陷阱:把"执行阶段代码"写到了"配置阶段"

新手最常见的 bug:

kotlin
// ❌ 错的写法
tasks.register("badTask") {
    println("Building...")             // ← 配置阶段就跑了,不对!
    val result = computeSomething()    // ← 配置阶段就计算了,不对!
    file("output.txt").writeText("$result")  // ← 配置阶段就写文件了,不对!
}

// ✅ 正确写法
tasks.register("goodTask") {
    doLast {
        println("Building...")
        val result = computeSomething()
        file("output.txt").writeText("$result")
    }
}

怎么验证?跑 ./gradlew tasks(没要求执行 badTask),如果你看到了 "Building..." 输出,说明你犯了这个错。

📌 核心原则注册 Task 的闭包里只放"配置 Task 自己的元数据"(如 dependsOndescriptioninputs.file(...)),真正的"动作"必须放进 doLast / doFirst

3.5.4 为什么 dependencies {} 能在配置阶段就解析?

很多人疑惑:

kotlin
dependencies {
    implementation("org.springframework:spring-core:6.1.1")
}

这是配置阶段执行的,意味着"声明依赖"是配置阶段干的。但实际下载依赖、解析依赖图首次需要时(lazy)。比如:

  • compileJava 任务执行时需要 classpath → 这时才解析
  • 用户跑 ./gradlew dependencies → 这时才解析

📌 要点:声明 ≠ 解析。声明在配置阶段,解析在按需触发。

3.5.5 配置阶段不要做的事

反模式为什么坏正确做法
在脚本顶层读文件 / 网络每次构建都跑,即使不需要写到 Task 的 doLast 里
在 register 闭包里直接干活同上写到 doLast / doFirst
滥用 afterEvaluate {}推迟配置不可控,调试困难用 Provider/Property API
tasks.getByName("xxx") { ... } 无脑修改触发 task 立刻配置(破坏 lazy)tasks.named("xxx") { ... }

3.6 Task 类型 vs 任意 Task

3.6.1 简单 Task vs Typed Task

kotlin
// 简单 Task(没有特定类型,只能用 doLast/doFirst)
tasks.register("simple") {
    doLast { println("simple") }
}

// Typed Task(有类型,能用类型自带的属性)
tasks.register<Copy>("copyDocs") {
    from("docs")               // ← Copy 类型自带 from 属性
    into(layout.buildDirectory.dir("docs"))
    include("**/*.md")
}

// 不写 register,等价于 doLast 形式
tasks.register("docsZip", Zip::class) {
    archiveFileName = "docs.zip"
    destinationDirectory = layout.buildDirectory.dir("dist")
    from("docs")
}

3.6.2 内置常用 Task 类型

类型用途典型属性
Copy拷贝文件from / into / include / rename
Delete删除文件delete
Zip / Tar打包归档archiveFileName / destinationDirectory
Exec运行外部命令commandLine / args
JavaExec运行 Java main classmainClass / classpath / args
Sync同步目录(多余的会删)from / into
TestJUnit 测试运行useJUnitPlatform / filter

3.6.3 一个综合例子:自定义打包任务

kotlin
// 拷贝 web 静态资源 → 编译 java → 打 zip 发布包
tasks.register<Copy>("copyWeb") {
    from("src/web")
    into(layout.buildDirectory.dir("dist/web"))
}

tasks.register<Zip>("releaseZip") {
    dependsOn("jar", "copyWeb")
    archiveFileName = "release-${project.version}.zip"
    destinationDirectory = layout.buildDirectory.dir("releases")

    from(layout.buildDirectory.dir("libs"))      // 拷 jar
    from(layout.buildDirectory.dir("dist"))      // 拷 web
    from("README.md")                            // 拷 readme
}

./gradlew releaseZip 就会得到一个完整的发布包。


3.7 命令行选 Task:: 路径写法

bash
# 单 Project 项目,直接 task 名
$ ./gradlew build

# 多 Project 项目,用 :path 指定子项目
$ ./gradlew :app:build              # 只跑 app 模块的 build
$ ./gradlew :lib-core:test          # 只跑 lib-core 的测试

# 跑多个
$ ./gradlew clean build

# 跳过某个 task
$ ./gradlew build -x test

# 强制执行(忽略 UP-TO-DATE)
$ ./gradlew build --rerun-tasks

3.8 章末小结

                    ★ 第 3 章核心知识图谱 ★

        ┌─────────────┬───────┴───────┬─────────────┐
        │             │               │             │
   ┌────▼────┐   ┌───▼────┐    ┌────▼────┐   ┌───▼─────┐
   │ Project │   │  Task  │    │ Plugin  │   │ Lifecycle│
   ├─────────┤   ├────────┤    ├─────────┤   ├──────────┤
   │ 一个产物 │   │ 动作单元 │    │ 功能套餐 │   │ Init     │
   │ 多模块   │   │ doFirst│    │ 核心     │   │ Config   │
   │ project()│   │ doLast │    │ 社区     │   │ Execute  │
   │          │   │ DAG    │    │ 自定义   │   │          │
   └─────────┘   └────────┘    └─────────┘   └──────────┘

                                       ★ 必须区分配置 vs 执行 ★

🎤 3.9 章末面试题(10 道高频题)

Q1. Gradle 的构建生命周期分哪几个阶段?

:3 个阶段:

  1. Initialization(初始化):读 settings.gradle.kts,确定有哪些 Project;
  2. Configuration(配置):执行所有 Project 的 build.gradle.kts,构造 Task 对象图(DAG)。注意:所有的 dependencies {}plugins {}tasks.register {} 都在这个阶段执行
  3. Execution(执行):按用户指定的 Task 反向追踪 DAG,依次执行需要的 Task 的实际动作(doFirst、doLast 里的代码)。

⚠️ 关键点:配置阶段每次构建都会完整跑,所以不要在配置阶段做耗时操作(读大文件、调网络)。Configuration Cache(配置缓存)能缓存这一阶段,第 9 章会讲。


Q2. 下面这段代码会输出什么?

kotlin
println("A")
tasks.register("foo") {
    println("B")
    doFirst { println("C") }
    doLast { println("D") }
}
println("E")

./gradlew foo 时输出顺序是?跑 ./gradlew tasks 呢?

./gradlew foo

A           ← 配置阶段
B           ← 配置阶段(register 闭包也是配置)
E           ← 配置阶段
> Task :foo
C           ← 执行阶段(doFirst)
D           ← 执行阶段(doLast)

./gradlew tasks

A           ← 配置阶段还是会跑
B           ← 配置阶段还是会跑
E           ← 配置阶段还是会跑
(C 和 D 不会跑,因为没执行 foo)

Q3. dependsOnmustRunAfterfinalizedBy 三个有什么区别?

  • dependsOn:强依赖。B dependsOn A 表示"跑 B 之前必须跑 A"。如果 A 不在执行列表,会被自动加进来。
  • mustRunAfter:弱顺序。B mustRunAfter A 表示"如果 A 和 B 都要跑,B 必须在 A 之后"。但单独跑 B 不会触发 A
  • finalizedBy:终结依赖。A finalizedBy B 表示"A 跑完后必须跑 B",即使 A 失败 B 也会跑。常用于资源清理(释放数据库连接、删临时文件)。
关系单独跑 B 会跑 A 吗顺序保证
dependsOn✅ 会✅ A → B
mustRunAfter❌ 不会仅在两者都执行时保证
finalizedBy/A 之后跑 B(A 失败也跑)

Q4. doFirstdoLast 的区别是什么?

Task 维护一个动作链表

  • doFirst { ... } 把动作 加到链表头(多个 doFirst 倒序执行);
  • doLast { ... } 把动作 加到链表尾(多个 doLast 顺序执行)。

执行时整个链表按顺序跑。所以同一个 Task 的多个 doFirst / doLast 顺序是:

后写的 doFirst → ... → 第一个 doFirst → 第一个 doLast → ... → 后写的 doLast

💡 实战建议:99% 时候用 doLast 就够了。doFirst 主要用来"在某个 Task 执行前插入打印 / 校验"。


Q5. tasks.registertasks.create 有什么区别?

  • tasks.create("foo") { ... }立即创建并配置 Task —— 配置阶段一遇到就跑闭包。
  • tasks.register("foo") { ... }懒注册 —— 只把"配置闭包"存起来,等真正需要这个 Task 时才执行。

优先用 register,因为:

  1. 构建快:用不到的 Task 不配置;
  2. 配置缓存友好:register 是 Configuration Cache 推荐写法;
  3. 现代标准:Gradle 5+ 的官方推荐。

Q6. 为什么不要在 build.gradle.kts 顶层读文件 / 调用网络?

:因为顶层代码在配置阶段执行,每次构建都会完整跑(即使你只是 ./gradlew tasks 列出任务)。结果:

  1. 拖慢每次构建(哪怕几秒,乘以一天 100 次构建就是几分钟);
  2. 破坏 Configuration Cache:网络 / 文件 IO 不是 deterministic,缓存机制无法处理;
  3. 难以测试:如果网络挂了,所有 ./gradlew 命令全挂。

正确做法:把这种逻辑放进自定义 Task 的 doLast,并用 inputs/outputs 声明输入输出 → 增量构建友好。


Q7. plugins { } 和 apply plugin: 'xxx' 有什么区别?

  • plugins {}(DSL 块):现代推荐写法。优点:

    • 类型安全:IDE 立即识别 plugin 提供的扩展(DSL 智能提示);
    • 自动管理 classpath:不需要手动 buildscript dependencies;
    • Plugin Portal 自动解析id("xxx") version "yyy" 一行搞定;
    • 可以静态分析:Gradle 能在 init 阶段就知道项目用了哪些插件。
  • apply(plugin = "xxx")(旧 API):老语法。需要先在 buildscript {} 里 classpath 上插件 jar,然后 apply。不要在新代码里用


Q8. 多个子模块共享配置,subprojects {} 和 Convention Plugin 哪个好?

Convention Plugin 更好

subprojects {} 的问题:

  1. 根 build.gradle 越写越长,难以维护;
  2. 所有子模块都被强制配置(即使只用一部分);
  3. 配置代码无法复用到其他项目;
  4. 影响并行配置 / 配置缓存。

Convention Plugin(写在 buildSrc/build-logic/ 里):

  1. 配置封装到 Kotlin 类,可单测;
  2. 子模块按需 plugins { id("my.convention") }
  3. 跨项目复用;
  4. 性能更好(懒加载)。

第 8 章会有完整示例。


Q9. 怎么看一个 Task 到底依赖了哪些 Task?

:4 种方式:

  1. --dry-run / -m:打印执行顺序但不跑

    bash
    $ ./gradlew build -m
  2. taskTree 插件(推荐):可视化依赖树

    kotlin
    plugins { id("com.dorongold.task-tree") version "2.1.1" }
    bash
    $ ./gradlew build taskTree
  3. --scan:上传到 Gradle 公共服务,浏览器里看完整依赖图

    bash
    $ ./gradlew build --scan
  4. 手动看插件源码 —— 老办法。


Q10. project.afterEvaluate {} 是干啥的?什么时候用?

afterEvaluate {} 注册一个回调,在当前 Project 的配置阶段结束后执行。常见场景:

kotlin
afterEvaluate {
    // 这时所有 plugins / extensions 都已经配好了
    val springVersion = the<SpringBootExtension>().version
    println("Spring Boot version: $springVersion")
}

什么时候用

  • 需要读取别的插件配置完之后才有的属性
  • 配置依赖关系时需要等所有 plugin 完成

什么时候 不要

  • 大多数情况下用 Provider / Property API 替代(lazy 而非延迟)。
  • 滥用 afterEvaluate 会让构建逻辑像意大利面 —— 别人看不懂啥时候配啥。

💡 现代最佳实践:优先 Provider API → 其次 register 的 lazy 配置 → 万不得已才 afterEvaluate


下一章 → 第 4 章 · 构建脚本 DSL:Groovy vs Kotlin →

🎬 可视化演示

演示加载缓慢或样式异常?点此在新标签页打开 ↗

💻 示例代码

txt
/*
 * 第 3 章 · 核心概念演示 — build.gradle.kts
 *
 * 这个脚本演示了 Project / Task / Plugin / 生命周期的所有核心点。
 * 跑下面任意一条命令,对照注释观察输出:
 *
 *   ./gradlew tasks                  → 看到所有自定义 task
 *   ./gradlew lifecycleDemo          → 看 doFirst/doLast 顺序
 *   ./gradlew chained                → 看 dependsOn 触发链
 *   ./gradlew pipeline               → 看复杂 DAG
 *   ./gradlew clean lifecycleDemo    → 看多个 task 的执行顺序
 */

plugins {
    base   // base 插件提供 clean / assemble / check / build 这些聚合任务
}

// ============================================================================
// 1. 配置阶段会执行的代码(脚本顶层)
// ============================================================================
println("[CONFIG] 我在配置阶段就执行了!只要你跑任何 ./gradlew 命令我都会跑")
println("[CONFIG] Project name: " + project.name)
println("[CONFIG] Project version: " + project.version)

// ============================================================================
// 2. 演示:doFirst / doLast 的顺序
// ============================================================================
tasks.register("lifecycleDemo") {
    group = "ch03-demo"
    description = "演示 doFirst / doLast 顺序"

    println("[CONFIG] register 闭包里的 println 也是配置阶段")

    // 注册多个 doFirst(后加的在前)
    doFirst { println("[EXEC] doFirst-A (第一次写)") }
    doFirst { println("[EXEC] doFirst-B (第二次写)") }
    doFirst { println("[EXEC] doFirst-C (第三次写) ← 最后写的会最先执行") }

    // 注册多个 doLast(按写入顺序)
    doLast  { println("[EXEC] doLast-X (第一次写) ← 第一次写的会最先执行") }
    doLast  { println("[EXEC] doLast-Y (第二次写)") }
    doLast  { println("[EXEC] doLast-Z (第三次写)") }
}

// ============================================================================
// 3. 演示:Task 之间的依赖(dependsOn)
// ============================================================================
tasks.register("step1") {
    group = "ch03-demo"
    doLast { println("⚙️  Step 1 done") }
}

tasks.register("step2") {
    group = "ch03-demo"
    dependsOn("step1")
    doLast { println("⚙️  Step 2 done") }
}

tasks.register("step3") {
    group = "ch03-demo"
    dependsOn("step2")
    doLast { println("⚙️  Step 3 done") }
}

tasks.register("chained") {
    group = "ch03-demo"
    description = "跑这个会触发 step1 → step2 → step3"
    dependsOn("step3")
    doLast { println("✅ All steps complete") }
}

// ============================================================================
// 4. 演示:mustRunAfter(弱顺序)
// ============================================================================
tasks.register("test1") {
    group = "ch03-demo"
    doLast { println("🧪 test1 running...") }
}

tasks.register("test2") {
    group = "ch03-demo"
    mustRunAfter("test1")  // 仅在两者都跑时,test2 必须在 test1 之后
    doLast { println("🧪 test2 running...") }
}

// ============================================================================
// 5. 演示:finalizedBy(终结依赖)
// ============================================================================
tasks.register("upload") {
    group = "ch03-demo"
    finalizedBy("cleanup")
    doLast { println("📤 Uploading... (假装上传文件)") }
}

tasks.register("cleanup") {
    group = "ch03-demo"
    doLast { println("🧹 Cleanup running... (即使 upload 失败也会跑)") }
}

// ============================================================================
// 6. 演示:典型 Typed Task(Copy)
// ============================================================================
tasks.register<Copy>("copyDocs") {
    group = "ch03-demo"
    description = "演示 Copy 类型的 Task"

    from("src")
    into(layout.buildDirectory.dir("copied-src"))
    include("**/*.kt", "**/*.java")
}

// ============================================================================
// 7. 综合演示:模拟一个 CI pipeline
// ============================================================================
tasks.register("compile") { group = "pipeline"; doLast { println("📦 compile") } }
tasks.register("unitTest") { group = "pipeline"; dependsOn("compile"); doLast { println("✅ unitTest") } }
tasks.register("lint")     { group = "pipeline"; dependsOn("compile"); doLast { println("🔍 lint") } }
tasks.register("packageApp") {
    group = "pipeline"
    dependsOn("compile")
    doLast { println("📦 packageApp") }
}
tasks.register("deploy") {
    group = "pipeline"
    dependsOn("unitTest", "lint", "packageApp")  // 三个并行依赖
    doLast { println("🚀 deploy DONE") }
}

tasks.register("pipeline") {
    group = "ch03-demo"
    description = "一个完整的 mock CI 流水线"
    dependsOn("deploy")
}

// ============================================================================
// 8. 演示:用循环动态生成 Task
// ============================================================================
val features = listOf("auth", "billing", "notification", "payment", "search")
features.forEach { feature ->
    tasks.register("test-" + feature) {
        group = "feature-test"
        doLast { println("🧪 Testing feature: " + feature) }
    }
}

// 一个聚合任务,按顺序触发所有 feature 测试
tasks.register("testAllFeatures") {
    group = "ch03-demo"
    description = "测试所有 feature"
    dependsOn(features.map { "test-" + it })
}

println("[CONFIG] 脚本结尾的 println,配置阶段执行")
txt
rootProject.name = "ch03-core-concepts-demo"

build.gradle.kts ↗ · settings.gradle.kts ↗