主题
第 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.kts7.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.kts7.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 各有什么优缺点?
答:
| 维度 | buildSrc | build-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 Plugin(
xxx.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 的好处:
- 配置阶段不强求值
- 可以接 Provider 链
- 配置缓存友好
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 会:
- 自动应用
kotlin("jvm")—— 让你写 Kotlin 代码; - 自动应用
java-gradle-plugin—— 让你能注册插件; - 自动加
gradleApi()和kotlin-stdlib到 classpath; - 生成 type-safe accessors(让你在 plugin 内部能用
tasks.test { ... }这种短写法)。
写自定义插件必装。
Q9. 怎么发布自定义插件到 Plugin Portal?
答:3 步:
- 申请 API Key:登录 https://plugins.gradle.org/u/me,新建一个 API key,写到
~/.gradle/gradle.properties:
properties
gradle.publish.key=YOUR_KEY
gradle.publish.secret=YOUR_SECRET- 配置 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"
}
}
}- 发布:
bash
$ ./gradlew publishPlugins第一次会进审核(约 1-2 天),后续发布即时生效。
Q10. 多模块项目里,Convention Plugin 该放在 buildSrc 还是 build-logic?
答:新项目首选 build-logic,理由:
- 修改 convention 不触发主项目所有模块重新配置 —— 大型项目(50+ 模块)能省好几秒;
- 能定义多个 convention(按 java、android、kotlin 拆分);
- 行业最佳实践(Google NowInAndroid、Cash App、Square 等都在用);
- 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 ↗