12、自定义Plugin:创建自定义插件(脚本插件、buildSrc、独立项目)、扩展Extension、发布插件

说实话,Gradle 的插件机制是我最喜欢它的地方之一。你想想看,项目里那些重复的配置——打 Jar 包、代码检查、版本管理——如果能封装成一个插件,一行代码就能复用,那得多爽?

这一章我们就来聊聊怎么自己写插件。我会从最简单的脚本插件开始,一步步带你走到发布独立插件的完整流程。嗯,这里要注意,插件不光是写代码,怎么设计扩展(Extension)让用户能配置,才是真正体现功力的地方。

核心知识点:
  • 三种插件形态:脚本插件、buildSrc 插件、独立项目插件
  • Extension 的设计与实现
  • 插件的发布与使用

12.1 脚本插件:最轻量的方式

脚本插件说白了就是一个 .gradle 文件。你把它抽出来,然后其他地方用 apply from 引入。我在小项目里经常这么干,省事。

举个例子,假设我们有个 common.gradle 文件:

// common.gradle
ext {
    projectVersion = '1.0.0'
    javaVersion = JavaVersion.VERSION_17
}

tasks.register('hello') {
    doLast {
        println "Hello from common script! Version: $projectVersion"
    }
}

然后在 build.gradle 里引入:

// build.gradle
apply from: 'common.gradle'

tasks.named('hello').configure {
    dependsOn 'build'
}

这种方式的好处是简单直接。但缺点也很明显——没有类型安全,IDE 没法给你代码提示。我曾经在一个 20 多个模块的项目里用脚本插件管理公共配置,后来改得多了,每次都要全局搜索替换,那叫一个痛苦。

我的建议:脚本插件适合小团队、小项目,或者临时封装一些配置。一旦项目复杂了,赶紧换成 buildSrc 或独立插件。

12.2 buildSrc 插件:项目内复用利器

buildSrc 是 Gradle 的一个特殊目录。放在这个目录下的代码,会自动编译并加入到项目的 classpath 中。说白了,就是项目内所有模块都能直接用。

目录结构长这样:

project-root/
├── buildSrc/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── java/
│               └── com/
│                   └── example/
│                       └── MyPlugin.groovy
├── app/
│   └── build.gradle
└── settings.gradle

buildSrc 的 build.gradle.kts 很简单:

// buildSrc/build.gradle.kts
plugins {
    `java-gradle-plugin`
}

repositories {
    mavenCentral()
}

然后写插件类:

// buildSrc/src/main/java/com/example/MyPlugin.groovy
package com.example

import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        project.task('myTask') {
            doLast {
                println "Hello from buildSrc plugin!"
            }
        }
    }
}

在 app/build.gradle 里使用:

plugins {
    id 'com.example.my-plugin'
}

注意,buildSrc 的插件 ID 默认是类的全限定名。你也可以在 buildSrc 的 build.gradle 里配置 gradlePlugin 块来指定 ID。

我曾经踩过的坑:buildSrc 的改动会导致整个项目的配置阶段重新执行。如果你的项目很大,每次改 buildSrc 代码都要等很久。所以,buildSrc 适合放一些稳定的、不常改的插件逻辑。

12.3 独立项目插件:真正的复用

如果你想把插件分享给多个项目,甚至发布到公共仓库,那就得用独立项目的方式。说白了,就是单独建一个 Gradle 项目,专门用来开发插件。

目录结构:

my-plugin/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
    └── main/
        └── java/
            └── com/
                └── example/
                    └── MyPlugin.groovy

build.gradle.kts:

plugins {
    `java-gradle-plugin`
    `maven-publish`
}

group = 'com.example'
version = '1.0.0'

gradlePlugin {
    plugins {
        register("myPlugin") {
            id = "com.example.my-plugin"
            implementationClass = "com.example.MyPlugin"
        }
    }
}

publishing {
    publications {
        create<MavenPublication>("pluginMaven") {
            from(components["java"])
        }
    }
    repositories {
        maven {
            url = uri("https://your-repo.com/releases")
        }
    }
}

发布命令:

./gradlew publish

使用方在 settings.gradle.kts 里添加仓库:

pluginManagement {
    repositories {
        maven {
            url = uri("https://your-repo.com/releases")
        }
        gradlePluginPortal()
    }
}

然后在 build.gradle 里:

plugins {
    id 'com.example.my-plugin' version '1.0.0'
}

12.4 扩展 Extension:让插件可配置

插件如果写死了,那跟脚本插件有什么区别?真正的插件应该让用户能自定义行为。这就需要 Extension 了。

说白了,Extension 就是一个数据类,用户可以在 build.gradle 里配置它,插件在运行时读取这些配置。

先定义 Extension 类:

// src/main/java/com/example/MyExtension.groovy
package com.example

class MyExtension {
    String message = "Default message"
    boolean verbose = false
}

然后在插件里注册它:

class MyPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        // 注册扩展
        def extension = project.extensions.create('myConfig', MyExtension)

        project.task('myTask') {
            doLast {
                println "Message: ${extension.message}"
                if (extension.verbose) {
                    println "Verbose mode enabled"
                }
            }
        }
    }
}

用户使用:

myConfig {
    message = "Hello from user config!"
    verbose = true
}

是不是很直观?我个人习惯把 Extension 设计成嵌套的,比如支持多个子配置块,这样用户用起来更灵活。

设计建议:Extension 的属性尽量用 Property<T> 类型,这样支持延迟赋值和任务依赖。Gradle 官方推荐的做法。

12.5 知识体系图

下面这张图帮你理清三种插件形态的关系和适用场景:

Gradle 自定义插件三种形态 脚本插件 apply from: 'xxx.gradle' 无类型安全 适合小项目 复用性:低 buildSrc 插件 项目内复用 有类型安全 改动触发全量重配 复用性:中 独立项目插件 跨项目复用 可发布到仓库 支持版本管理 复用性:高 核心流程 1. 实现 Plugin<Project> 接口 2. 注册 Extension(可选) 3. 注册 Task,读取 Extension 配置 4. 发布(独立项目)或直接使用(buildSrc/脚本) Extension 设计要点: • 使用 Property<T> 类型 • 支持嵌套配置 • 提供默认值

12.6 发布插件到 Gradle Plugin Portal

如果你想把自己的插件分享给全世界,可以发布到 Gradle Plugin Portal。流程也不复杂:

  1. plugins.gradle.org 注册账号
  2. 获取 API Key
  3. 在 build.gradle 里配置发布插件
plugins {
    id 'com.gradle.plugin-publish' version '1.2.1'
}

pluginBundle {
    website = 'https://github.com/your/project'
    vcsUrl = 'https://github.com/your/project.git'
    description = 'A plugin that does awesome things'
    tags = ['gradle', 'plugin', 'example']
    
    plugins {
        myPlugin {
            displayName = 'My Awesome Plugin'
        }
    }
}

然后执行:

./gradlew publishPlugins

发布成功后,全世界的人都能通过 plugins DSL 使用你的插件了。

注意:发布到 Plugin Portal 的插件必须开源,并且要有完整的文档。我见过有人发布了一个半成品,结果被用户追着提 issue,那场面...嗯,你懂的。

12.7 避坑指南

  • 我曾经犯过的错:在插件里直接修改 Project 的默认配置,导致用户无法覆盖。后来我改成用 Extension 让用户显式配置,问题就解决了。
  • 另一个坑:插件里用了全局变量,导致多个项目同时构建时出现冲突。记住,插件应该是无状态的,每次 apply 都是独立的。
  • 还有一个:发布插件时忘记配置最小 Gradle 版本,用户用低版本 Gradle 构建直接报错。现在我会在插件里加一个 project.gradleVersion 检查。

好了,自定义插件这块就聊这么多。从脚本插件到独立项目,再到 Extension 的设计,每一步都有它的适用场景。我个人建议,只要项目超过 3 个模块,就值得用 buildSrc 或独立插件来管理公共逻辑。别等到项目乱成一锅粥了再重构,那时候成本就高了。


公众号:蓝海资料掘金营,微信deep3321