插件开发:自定义插件基础、插件扩展、插件发布与复用、约定优于配置实践
说实话,Gradle 插件开发是我觉得整个构建工具进阶里最有意思的一块。为什么?因为插件能让你把重复的构建逻辑封装起来,一次写好,到处复用。我刚开始接触 Gradle 时,项目里 build.gradle 动辄几百行,各种 task 堆在一起,看着就头疼。后来学会了写插件,才真正体会到什么叫「一劳永逸」。
这一章,我们就来聊聊怎么从零开始写一个 Gradle 插件。我会把我在实际项目中踩过的坑、总结的经验都抖出来。
自定义插件基础:从最简单的开始
Gradle 插件本质上就是一个实现了 Plugin<Project> 接口的类。你想想看,它就是一个「钩子」,挂到项目上就能干活。
先看一个最简单的例子:
// buildSrc/src/main/java/com/example/HelloPlugin.java
public class HelloPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.task("hello") {
it.doLast {
System.out.println("Hello from plugin!")
}
}
}
}
然后在 build.gradle 里引用:
plugins {
id 'com.example.hello'
}
运行 gradle hello,就能看到输出。嗯,就是这么简单。
buildSrc 目录下,这样项目内可以直接引用,不需要发布。等插件稳定了,再考虑发布到仓库。
不过,实际项目中插件不会这么简单。你通常需要让用户能配置一些参数。这就引出了插件扩展。
插件扩展:让用户能配置你的插件
插件扩展说白了就是给用户留几个「旋钮」,让他们能调整插件的行为。我见过很多新手写插件,把所有配置都硬编码在代码里,结果换个项目就得改源码——这显然不行。
来看一个带扩展的插件:
// 定义扩展类
public class GreetingExtension {
private String message = "Hello"
private String recipient = "World"
// getter 和 setter 省略
}
// 在插件中注册扩展
public class GreetingPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// 注册扩展,名字叫 'greeting'
project.getExtensions().create("greeting", GreetingExtension.class)
project.task("sayHello") {
it.doLast {
GreetingExtension ext = project.getExtensions().getByType(GreetingExtension.class)
System.out.println("${ext.message}, ${ext.recipient}!")
}
}
}
}
用户在 build.gradle 里就可以这样配置:
greeting {
message = "你好"
recipient = "Gradle"
}
运行 gradle sayHello,输出就是「你好, Gradle!」。
recipient,DSL 里就得写 recipient,不能写成 receiver。Gradle 是通过反射来匹配的,名字对不上就会报错。
插件发布与复用:把你的插件分享出去
插件写好了,怎么给别人用?发布到 Gradle Plugin Portal 或者私有仓库。我个人习惯先发布到公司内部的 Nexus 或 Artifactory 上,等验证没问题了再考虑公开。
发布插件的步骤大致如下:
- 在
build.gradle中应用maven-publish和java-gradle-plugin插件 - 配置插件标识(plugin ID)和版本号
- 配置发布仓库地址
- 执行
gradle publish
一个典型的发布配置:
plugins {
id 'java-gradle-plugin'
id 'maven-publish'
}
gradlePlugin {
plugins {
greetingPlugin {
id = 'com.example.greeting'
implementationClass = 'com.example.GreetingPlugin'
}
}
}
publishing {
repositories {
maven {
url = uri('https://nexus.example.com/repository/gradle-plugins/')
}
}
}
发布后,其他项目就可以通过 plugins 块引用了:
plugins {
id 'com.example.greeting' version '1.0.0'
}
com.example。别用太通用的名字,容易冲突。
约定优于配置:让插件更「聪明」
「约定优于配置」是 Gradle 的核心哲学之一。说白了就是:你按我的约定来,就不用写那么多配置。比如 Spring Boot 插件,你只要把源码放在 src/main/java 下,它自动就能编译打包,你什么都不用配。
我在写插件时,也会尽量遵循这个原则。举个例子,假设我要写一个代码检查插件,约定如下:
- 检查规则文件放在
config/checkstyle/目录下 - 报告输出到
build/reports/目录 - 如果用户没有提供规则文件,使用内置的默认规则
这样用户只需要应用插件,什么都不用配就能跑起来。如果用户想自定义,再通过扩展去覆盖。
来看一个实现片段:
public class ConventionPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// 约定:源码目录
File srcDir = project.file("src/main/java")
if (!srcDir.exists()) {
srcDir.mkdirs()
}
// 约定:输出目录
File outputDir = project.file("${project.buildDir}/output")
// 注册 task,使用约定的目录
project.task("process") {
it.doLast {
System.out.println("处理源码: ${srcDir}")
System.out.println("输出到: ${outputDir}")
}
}
}
}
用户只需要:
plugins {
id 'com.example.convention'
}
就能直接运行 gradle process。是不是很清爽?
知识体系一览
下面这张图总结了插件开发的核心脉络,你可以对照着回顾一下:
这张图把插件开发的四个核心模块串起来了。从基础实现到扩展配置,再到发布复用,最后用「约定优于配置」来提升用户体验。你写插件时,可以按这个脉络一步步来。