Skip to content

第 7 章 Plugin 插件体系:从用插件到造插件

学习目标:搞懂 Gradle 三种插件类型(Core / Community / 自定义);会用 plugins {} DSL;能写一个简单的自定义 plugin(buildSrc / build-logic / 单独项目发布);理解 Convention Plugin 是大型项目"减少模板代码"的利器。


7.1 为什么要用 Plugin?

回顾第 3 章:没有 Plugin,每个 Java 项目都要自己写 compileJava、jar、test 这些 task。Plugin 把"通用功能套餐"打包,一行 plugins { java } 就拥有完整能力。

   没有 Plugin 的世界               有 Plugin 的世界
   ─────────────────                ─────────────────

   tasks.register("compile") {     plugins { java }
       // ... 50 行 ...               // ☝ 一行搞定
   }
   tasks.register("jar") {           // 自动注册:
       // ... 30 行 ...               // - compileJava
   }                                  // - processResources
   tasks.register("test") {           // - jar
       // ... 40 行 ...               // - test
   }                                  // - check / build / clean
   ...                                // - SourceSet 约定
                                      // - implementation/api/...

7.2 三大插件类型

7.2.1 Core Plugin(Gradle 自带)

直接用,无需声明版本

kotlin
plugins {
    java                       // Java 编译
    application                // 可执行 JVM 应用
    `java-library`             // Java 库(含 api)
    `maven-publish`            // 发布到 Maven 仓库
    signing                    // 签名
    jacoco                     // 代码覆盖率
    base                       // 最基础的:clean / assemble / check
    `jvm-test-suite`           // 多套测试集
}

完整列表:https://docs.gradle.org/current/userguide/plugin_reference.html

7.2.2 Community Plugin(社区插件)

来自 Plugin Portal必须声明版本

kotlin
plugins {
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
    id("com.github.ben-manes.versions") version "0.51.0"
    kotlin("jvm") version "1.9.20"
    kotlin("plugin.spring") version "1.9.20"
}

7.2.3 自定义 Plugin(项目内)

3 种放法:

位置用途复用范围
buildSrc/单项目内复用仅本项目
build-logic/(推荐)单项目内复用,更现代仅本项目
独立项目 + 发布到仓库跨项目复用整个公司/全球

7.3 plugins {} DSL vs apply

7.3.1 现代写法(plugins DSL)

kotlin
plugins {
    id("org.springframework.boot") version "3.2.0"
}

优点

  • 声明式 + 类型安全
  • 自动从 Plugin Portal 下载
  • 启用 type-safe accessors(IDE 智能补全)
  • Gradle 能在 init 阶段就知道用了哪些插件

7.3.2 老语法(apply)

kotlin
buildscript {
    repositories { mavenCentral() }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:3.2.0")
    }
}
apply(plugin = "org.springframework.boot")

何时用 apply

  • 看老项目维护
  • 在 Convention Plugin 里需要 apply 别的插件

新代码不要写 buildscript {} + apply 的老组合

7.3.3 把 apply 转 plugins

kotlin
// ❌ 老
buildscript {
    repositories { mavenCentral() }
    dependencies { classpath("io.freefair.gradle:lombok-plugin:8.4") }
}
apply(plugin = "io.freefair.lombok")

// ✅ 新
plugins {
    id("io.freefair.lombok") version "8.4"
}

7.4 自定义插件 - 路径 1:buildSrc

7.4.1 buildSrc 是什么

在项目根创建一个 buildSrc/ 目录,Gradle 会自动把它当成构建逻辑代码编译,编译产物可以在所有子模块的 build.gradle.kts 里直接用。

my-project/
├── buildSrc/                          ★ 特殊目录!
│   ├── build.gradle.kts                — buildSrc 自己的构建脚本
│   └── src/main/kotlin/
│       └── my-java-conventions.gradle.kts   ← 一个 Convention Plugin
├── app/
│   └── build.gradle.kts                — 这里可以 plugins { id("my-java-conventions") }
└── lib/
    └── build.gradle.kts

7.4.2 写一个 Convention Plugin

buildSrc/build.gradle.kts

kotlin
plugins {
    `kotlin-dsl`     // 让 buildSrc 支持写 Kotlin DSL 插件
}

repositories {
    mavenCentral()
    gradlePluginPortal()
}

buildSrc/src/main/kotlin/my-java-conventions.gradle.kts

kotlin
// 文件名 = plugin id(去掉 .gradle.kts)
// 这里我们叫 "my-java-conventions"

plugins {
    java
}

group = "com.example"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    "testImplementation"("org.junit.jupiter:junit-jupiter:5.10.0")
    "testRuntimeOnly"("org.junit.platform:junit-platform-launcher")
}

tasks.named<Test>("test") {
    useJUnitPlatform()
}

// 给所有 JavaCompile 加统一编码
tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
}

7.4.3 在子模块里用

app/build.gradle.kts

kotlin
plugins {
    id("my-java-conventions")    // ← 直接用!不需要版本号
}

dependencies {
    implementation("org.apache.commons:commons-lang3:3.13.0")
}

./gradlew :app:build —— 自动应用所有 convention 配置。

7.4.4 buildSrc 的"魔法"

  • 自动被 Gradle 当成构建逻辑编译
  • 不需要声明版本号(因为是项目内的)
  • 修改 buildSrc 会导致所有子模块重新配置(影响构建速度)

7.5 自定义插件 - 路径 2:build-logic(推荐)

buildSrc 有个缺点:任何修改都触发所有子模块重新配置,大型项目很慢。

Gradle 推荐的现代方案:用 includeBuild 把 build 逻辑做成 Composite Build

7.5.1 目录结构

my-project/
├── build-logic/                       ← 普通的 Gradle 项目(不是 buildSrc)
│   ├── settings.gradle.kts             — build-logic 自己的 settings
│   ├── convention/
│   │   ├── build.gradle.kts            — convention 子模块的 build
│   │   └── src/main/kotlin/
│   │       └── my-java-conventions.gradle.kts
├── settings.gradle.kts                 — 主项目的 settings
└── app/
    └── build.gradle.kts

7.5.2 主项目 settings.gradle.kts

kotlin
pluginManagement {
    includeBuild("build-logic")    // ★ 把 build-logic 当作 build
    repositories {
        gradlePluginPortal()
    }
}

rootProject.name = "my-project"
include("app")

7.5.3 build-logic/settings.gradle.kts

kotlin
dependencyResolutionManagement {
    repositories { mavenCentral() }
}

rootProject.name = "build-logic"
include("convention")

7.5.4 build-logic/convention/build.gradle.kts

kotlin
plugins {
    `kotlin-dsl`
}

dependencies {
    // 如果你的 convention 要用 Kotlin / Spring Boot 等插件的能力
    // implementation(libs.kotlin.gradle.plugin)
    // implementation(libs.spring.boot.gradle.plugin)
}

gradlePlugin {
    plugins {
        register("javaConventions") {
            id = "my.java-conventions"
            implementationClass = "com.example.JavaConventionsPlugin"
        }
    }
}

或者直接放 .gradle.kts precompiled scripts(更简洁)。

7.5.5 好处

  • 修改 convention 只触发 build-logic 重编,不影响主项目其他模块
  • 可以单元测试 convention 逻辑
  • 跨项目可以 git submodule 共享 build-logic

💡 Google 官方示例 NowInAndroid 就是用 build-logic 模式,是学习现代 Gradle 工程化的样板。


7.6 写一个真正的"自定义 Plugin 类"

前面讲的 Precompiled Script Plugin(.gradle.kts 文件)是简化形式。完整的 Plugin 是写一个 Kotlin/Java 类:

kotlin
// build-logic/convention/src/main/kotlin/MyJavaConventions.kt

package com.example

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.jvm.toolchain.JavaLanguageVersion

class MyJavaConventions : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            // 1. 应用基础插件
            pluginManager.apply("java")

            // 2. 配置 Java toolchain
            extensions.configure<JavaPluginExtension>("java") {
                toolchain.languageVersion.set(JavaLanguageVersion.of(17))
            }

            // 3. 注册仓库
            repositories.mavenCentral()

            // 4. 加默认依赖
            dependencies.add("testImplementation", "org.junit.jupiter:junit-jupiter:5.10.0")

            // 5. 配置 Test
            tasks.withType<Test>().configureEach {
                useJUnitPlatform()
            }

            // 6. 加自定义 task
            tasks.register("hello") {
                doLast { println("Hello from MyJavaConventions!") }
            }
        }
    }
}

注册到 gradlePlugin

kotlin
// build-logic/convention/build.gradle.kts
gradlePlugin {
    plugins {
        register("javaConventions") {
            id = "my.java-conventions"
            implementationClass = "com.example.MyJavaConventions"
        }
    }
}

7.7 把 Plugin 发到 Plugin Portal(公开发布)

写好了想分享给世界?发到 plugins.gradle.org

kotlin
// build.gradle.kts
plugins {
    `java-gradle-plugin`
    id("com.gradle.plugin-publish") version "1.2.1"
}

gradlePlugin {
    website = "https://github.com/me/my-plugin"
    vcsUrl = "https://github.com/me/my-plugin.git"
    plugins {
        create("myPlugin") {
            id = "com.example.my-plugin"
            displayName = "My Awesome Plugin"
            description = "Does something awesome"
            tags = listOf("kotlin", "android", "convention")
            implementationClass = "com.example.MyPlugin"
        }
    }
}

./gradlew publishPlugins(需要先在 Plugin Portal 申请 API key)。


7.8 Extension:让插件可配置

好的 Plugin 应该让用户配置参数。用 Extension 实现:

kotlin
// 1. 定义 Extension
interface MyPluginExtension {
    val outputDir: DirectoryProperty
    val verbose: Property<Boolean>
}

// 2. 在 Plugin 里注册
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        val ext = target.extensions.create("myPlugin", MyPluginExtension::class.java)

        target.tasks.register<MyTask>("myAction") {
            outputDir.set(ext.outputDir)
            verbose.set(ext.verbose)
        }
    }
}

用户使用:

kotlin
plugins {
    id("com.example.my-plugin")
}

myPlugin {
    outputDir = layout.buildDirectory.dir("my-output")
    verbose = true
}

7.9 章末小结

                    ★ 第 7 章核心知识图谱 ★

        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
   ┌────▼────┐          ┌────▼────┐          ┌────▼─────┐
   │ 应用插件 │          │ 自定义   │          │ 发布     │
   ├──────────┤          ├──────────┤          ├──────────┤
   │ Core    │          │ buildSrc │          │ Plugin   │
   │ Community│          │ build-   │          │ Portal   │
   │ Custom   │          │  logic   │          │          │
   │ plugins{}│          │ Plugin 类│          │ 公司私服 │
   └─────────┘          └──────────┘          └──────────┘

                  ★ Convention Plugin 是大型项目"消模板"利器 ★

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

Q1. Plugin DSL plugins { } 和老的 apply plugin: 'xxx' 区别?

  • plugins {}:现代推荐写法,类型安全,Gradle 在 init 阶段就知道项目用了哪些插件。优点:自动管理 classpath、type-safe accessors、声明式。
  • apply(plugin = "xxx"):老语法,要先在 buildscript 里 classpath 上插件 jar。新代码不要用。

apply(plugin = ...) 在 Convention Plugin 内部仍然会用(因为内部要动态 apply 别的插件)。


Q2. buildSrc 和 build-logic 各有什么优缺点?

维度buildSrcbuild-logic
配置零配置(约定目录)需要写 settings.gradle.kts、includeBuild
性能修改任何文件都触发所有子模块重新配置修改不触发主项目重新配置(更快)
复用性只能本项目用可以用 git submodule 跨项目共享
学习曲线中等
推荐场景小型项目 / 学习大型项目 / 生产

💡 Google NowInAndroid、Kotlin、Spring 等大型项目都用 build-logic 模式。


Q3. Convention Plugin 解决什么问题?

:解决多模块项目"配置重复"的问题。

没有 Convention Plugin

kotlin
// app/build.gradle.kts
plugins { java }
java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
repositories { mavenCentral() }
dependencies { testImplementation("...") }
tasks.test { useJUnitPlatform() }

// lib/build.gradle.kts —— 完全一样的 5 行配置!
plugins { java }
java { toolchain { ... } }
repositories { ... }
...

用 Convention Plugin

kotlin
// app/build.gradle.kts
plugins { id("my.java-conventions") }   // ← 一行搞定

// lib/build.gradle.kts
plugins { id("my.java-conventions") }

加新模块时,配置秒同步。是大型项目"工程化"的核心实践。


Q4. Precompiled Script Plugin 和写一个 Plugin 类有什么区别?

  • Precompiled Script Pluginxxx.gradle.kts 文件):直接用 Kotlin DSL 写,文件名 = plugin id,不需要 implementationClass。简单快速。
  • Plugin 类(实现 Plugin<Project> 接口):需要写 Kotlin/Java 类、注册到 gradlePlugin、声明 implementationClass。更可测、可发布

工程上:

  • 快速复用本项目配置 → Precompiled Script
  • 发布给其他项目用 → Plugin 类

Q5. Extension 和 Property 是什么关系?

  • Extension:插件给项目"加扩展",对应 build.gradle.kts 里的配置块(如 springBoot { ... })。
  • Property / Provider:Extension 内部存值的容器,懒求值
kotlin
interface MyExt {
    val name: Property<String>      // ← Property
    val outputDir: DirectoryProperty
}

用 Property 而不是直接 var name: String 的好处:

  1. 配置阶段不强求值
  2. 可以接 Provider 链
  3. 配置缓存友好

Q6. 一个 Plugin 怎么给 Project 加 Task?

kotlin
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.tasks.register("hello") {
            doLast { println("Hello from plugin!") }
        }
    }
}

通过 target.tasks.register(...) —— 跟 build.gradle.kts 里写 tasks.register(...) 是同一回事。

也可以加 typed task:

kotlin
target.tasks.register<Copy>("copyAssets") {
    from("assets")
    into(target.layout.buildDirectory.dir("assets"))
}

Q7. 怎么让 Plugin 可配置(用户能传参)?

:用 Extension:

kotlin
// 1. 定义 Extension 接口
interface MyExt {
    val source: Property<String>
    val verbose: Property<Boolean>
}

// 2. Plugin 注册 Extension
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        val ext = target.extensions.create<MyExt>("myExt")
        target.tasks.register<MyTask>("doIt") {
            source = ext.source
            verbose = ext.verbose
        }
    }
}

// 3. 用户配置
myExt {
    source = "src/data"
    verbose = true
}

Q8. kotlin-dsl 插件有什么作用?

:在 buildSrc / build-logic 里用 plugins { \kotlin-dsl` }` 后,Gradle 会:

  1. 自动应用 kotlin("jvm") —— 让你写 Kotlin 代码;
  2. 自动应用 java-gradle-plugin —— 让你能注册插件;
  3. 自动加 gradleApi()kotlin-stdlib 到 classpath;
  4. 生成 type-safe accessors(让你在 plugin 内部能用 tasks.test { ... } 这种短写法)。

写自定义插件必装。


Q9. 怎么发布自定义插件到 Plugin Portal?

:3 步:

  1. 申请 API Key:登录 https://plugins.gradle.org/u/me,新建一个 API key,写到 ~/.gradle/gradle.properties
properties
gradle.publish.key=YOUR_KEY
gradle.publish.secret=YOUR_SECRET
  1. 配置 plugin-publish
kotlin
plugins {
    `java-gradle-plugin`
    id("com.gradle.plugin-publish") version "1.2.1"
}
gradlePlugin {
    website = "https://github.com/me/x"
    vcsUrl = "https://github.com/me/x.git"
    plugins {
        create("xPlugin") {
            id = "com.example.x"
            displayName = "X Plugin"
            description = "..."
            tags = listOf("kotlin")
            implementationClass = "com.example.XPlugin"
        }
    }
}
  1. 发布
bash
$ ./gradlew publishPlugins

第一次会进审核(约 1-2 天),后续发布即时生效。


Q10. 多模块项目里,Convention Plugin 该放在 buildSrc 还是 build-logic?

新项目首选 build-logic,理由:

  1. 修改 convention 不触发主项目所有模块重新配置 —— 大型项目(50+ 模块)能省好几秒;
  2. 能定义多个 convention(按 java、android、kotlin 拆分);
  3. 行业最佳实践(Google NowInAndroid、Cash App、Square 等都在用);
  4. Composite Build 可以 git submodule 跨项目共享。

buildSrc 何时用:小项目、学习阶段、不想折腾 Composite Build 配置时。


下一章 → 第 8 章 · 多项目构建 & Composite Build →

🎬 可视化演示

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

💻 示例代码

txt
/*
 * app 模块 — 应用 Convention Plugin
 * 跑:./gradlew :app:conventionHello
 *     ./gradlew :app:build
 */

plugins {
    id("my-java-conventions")    // 来自 buildSrc,不需要版本号
    application
}

application {
    mainClass = "com.example.app.AppMain"
}

dependencies {
    implementation(project(":lib"))
    implementation("com.google.guava:guava:32.1.3-jre")
}
txt
/*
 * 第 7 章 · Plugin 演示 — buildSrc 自己的 build 脚本
 * 
 * kotlin-dsl 插件让本目录可以写 Kotlin 编写的 Gradle plugin。
 */
plugins {
    `kotlin-dsl`
}

repositories {
    gradlePluginPortal()
    mavenCentral()
}
txt
/*
 * 第 7 章 · Convention Plugin 示例
 *
 * 这个文件就是一个 Plugin,文件名(去掉 .gradle.kts)= plugin id
 * 子模块用:plugins { id("my-java-conventions") }
 */

plugins {
    java
}

group = "com.example.gradle.ch07"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    "implementation"("org.slf4j:slf4j-api:2.0.9")
    "testImplementation"("org.junit.jupiter:junit-jupiter:5.10.0")
    "testRuntimeOnly"("org.junit.platform:junit-platform-launcher")
}

tasks.named<Test>("test") {
    useJUnitPlatform()
}

tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
    options.compilerArgs.add("-Xlint:all")
}

// 给所有应用了此 convention 的项目自动加一个 hello task
tasks.register("conventionHello") {
    group = "ch07-demo"
    description = "证明 convention 已应用"
    doLast {
        println("✅ Hello from my-java-conventions! Project: " + project.name)
    }
}
txt
/*
 * lib 模块 — 也应用 Convention Plugin
 * 没有重复一行配置
 */

plugins {
    id("my-java-conventions")
    `java-library`
}

dependencies {
    api("org.apache.commons:commons-lang3:3.13.0")
}
txt
rootProject.name = "ch07-plugin-demo"
include("app", "lib")

app/build.gradle.kts ↗ · buildSrc/build.gradle.kts ↗ · buildSrc/src/main/kotlin/my-java-conventions.gradle.kts ↗ · lib/build.gradle.kts ↗ · settings.gradle.kts ↗