Gradle学习10——自定义Gradle插件

学习本系列前可以下载相关的github项目gradleLearnDemo
地址:https://github.com/sososeen09/gradleLearnDemo

Gradle插件分为两类:

  • 脚本插件,是一个普通的Gradle构建脚本,它可以被导入到其它的构建脚本中。
  • 对象插件,需要实现org.gradle.api.Plugin接口。

1 使用脚本插件

假设我们有一个脚本名为 sayhello.gradle ,它里面有一个task:

task sayHello << {
    println 'hello world'
}

然后我们在build.gradle 中依赖这个外部脚本,调用Project的apply方法,apply方法调用时传入from属性,它的值可以是任何类型的URL,比如HTTP地址。

apply from: 'sayhello.gradle'

此时我们运行gradle sayHello 命令,可以看到sayHello 这个task执行了:

:sayHello
hello world

通过apply from的方式引入脚本插件,就好像那个脚本插件中的内容是写在当前的build.gradle中一样。它是使用比较简单,这里就不多做介绍了。

2 对象插件

我们之前讲了自定义的task,它的实现逻辑是一种可维护、可测试的解决方案。通过打包成jar文件,task可以在独立的项目中被重用。然而,该方式仍有一些限制。
优点:

  • 定制逻辑在类中是自包含的,并且可以通过增强型task配置
  • 通过将task属性用注解标志可以支持声明式增量构建
  • 自定义的task可以测试

缺点:

  • 自定义的task仅仅暴露独立的工作单元。所提供的额外的公式化代码、约定和生命周期的整合并不是很直接
  • 自定义的task仅仅能通过增强型task来配置。通过自定义的DSL,缺乏有表达性的扩展机制
  • 其他插件的功能不容易使用或扩展

而使用对象插件的方式,可以给你最大的灵活性去封装高复杂度的逻辑,并且提供一种强大的扩展机制可以在构建脚本中定制它的行为。比如编译Java代码,我们会通过一句话来引入Java插件:

apply plugin :'java'

然后我们还可以配置很多属性来控制编译逻辑。这个Java插件就是一个对象插件。

3 对象插件的实现方式

对于实现一个对象插件,有4个基本元素是非常重要的。

  1. 在放置插件实现的位置方面Gradle给你完全的灵活性。代码可以放在构建脚本中或者buildSrc目录下,也可以作为一个独立的工程被开发并且以jar文件方式发布。

  2. 每个插件都需要提供一个实现类,它代表着插件的入口点。插件可以用任何JVM语言编写并编译成字节码。

  3. 应用到项目中的插件可以通过暴露出来的扩展对象进行定制。如果用户想要在构建脚本中覆盖插件的默认配置时,这一点特别有用。

  4. 插件描述符是一个属性文件,它包含了关于插件的元信息。通常,它包含有插件的简短名字和插件实现类的映射。

4 编写对象插件

编写一个插件最低的要求是提供org.gradle.api.Plugin<T>接口的一个实现类。该接口仅仅定义了一个简单的方法:apply(T target)。

现在我们来演示自定义一个插件,这个插件的作用是提供一个task来打印用户凭证。
我们前面已经提到,自定义对象插件和自定义task方式类似,代码可以放在构建脚本中或者buildSrc目录下,也可以作为一个独立的工程被开发并且以JAR文件方式发布,下面我们根据这三种方式来分别演示如何自定义和使用插件。

4.1 放在构建脚本中

在build.gradle中,代码如下:

apply plugin:CredentialPlugin

userCredential{
    username='admin'
    password='000000'
}

class CredentialPlugin implements Plugin<Project>{
    @Override
    void apply(Project project){
        project.extensions.create('userCredential',CredentialExtension)
        project.tasks.create('printUserCredential') << {
             println "username is: " + project.userCredential.username
             println "password is: " + project.userCredential.password
        }
    }
}

//扩展模型
class CredentialExtension {
    String username
    String password
}

Gradle将语言结构化模型作为扩展,扩展可以被添加到许多Gradle对象中,如果一个类实现了org.gradle.api.plugins.ExtensionAware接口,比如Project或者Task,就认为它是扩展可知的。每个扩展都是一个数据模型,它是扩展的基础。这个模型可以是一个POJO或者Groovy Bean。
在上面的代码中,userCredential闭包中的内容,可以从构建脚本中给task提供所需要的属性值,这个userCredential就是我们暴露的一个DSL。
ExtensionAware对象有一个方法getExtensions(),该方法返回一个ExtensionContainer对象,ExtensionContainer对象可以通过create()方法来注册我们的扩展,也就是把我们配置的DSL和具体的类关联起来。在本例中就是把userCredential这个闭包和CredentialExtension这个类关联起来,这就是插件的扩展机制。

扩展对象vs额外属性 被用来扩展一个对象的DSL的扩展是扩展可知的,一个已注册的扩展模型会暴露一些属性和方法,用来给构建脚本建立新的构建语言结构,这些属性名和方法在创建的时候已经定好。扩展模型的典型用例是插件。额外属性,是一些通过ext命名空间创建的简单变量,它们一般提供给用户空间也就是构建脚本使用,额外属性的属性名是可以任意指定的。请尽量避免在插件实现中使用额外属性。

4.2 放在buildSrc目录下

与自定义task一样,Groovy代码放在buildSrc/src/main/groovy 目录下,

package com.sososeen.credential

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

class CredentialPlugin implements Plugin<Project>{
    @Override
    void apply(Project project){
        ...
    }
}


package com.sososeen.credential
class CredentialExtension {
    ...
}

在与buildSrc同级的目录下,创建build.gradle脚本,引入这个plugin:

apply plugin:com.sososeen.credential.CredentialPlugin
userCredential{
    username='admin'
    password='000000'
}

然后执行 gradle printUserCredential 命令,可以看到打印结果:

:printUserCredential
username is: admin
password is: 000000

可以看到,我们引入这个插件的时候是把它的包名带类名写上了,这显得太长了,不好写。我们可以给这个插件一个有意义且精简的名字。在src/main/resources/META-INF/gradle-plugins目录下,我们可以创建一个属性文件来配置。比如创建一个credentials.properties,它就是一个插件描述符,暴露了插件名字是credentials。在这个文件中,将该插件类的全局类名赋值给键implemention-class,如下:

implementation-class = com.sososeen.credential.CredentialPlugin

然后应用插件就可以这样子:

apply plugin : 'credentials'

执行 gradle printUserCredential 命令,可以看到打印结果与之前一样。

目录结构如下:


buildSrc目录结构

有一点需要说明,Gradle可以用驼峰式的缩写在命令行上运行任务。但要保证缩写是唯一的。比如我们执行gradle printUserCredential,实际上可以缩写为gradle pUC,这对于任务名称比较长的时候特别有用,感兴趣的可以试一下。

4.3 以jar文件形式提供插件

这个步骤与自定义task打包为jar文件一样,新建一个项目,把buildSrc目录下的文件复制过来,同时,在该项目下创建一个build.gradle文件。

apply plugin: 'groovy' //应用这个插件来编译Groovy代码
apply plugin: 'maven'

version = '1.0'
group = 'com.sososeen09'
archivesBaseName = 'credential'

repositories {
    mavenCentral()
}

dependencies {
    // 使用Gradle中的API需要这个
    compile gradleApi()
}

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: "file:../lib")
        }
    }
}

执行gradle uploadArchives命令后就可以看到,在与当前项目同级的lib文件目录中生成了我们期望的jar文件。

我们再新建一个工程,这个工程中有一个脚本文件build.gradle:

apply plugin : 'credentials'
buildscript {
    repositories {
        maven {
            url 'file:../lib'
        }

    }

    dependencies {
        classpath 'com.sososeen09:credential:1.0'
    }
}

userCredential{
    username='admin'
    password='000000'
}

执行 gradle printUserCredential 命令,可以看到打印结果与之前一样。

目录结构如下:


以jar包形式提供依赖

5 总结

Gradle提供了一个丰富的插件生态系统来重用哪些开箱即用的标准插件和由社区提供的第三方插件。有两种类型的插件:脚本插件和对象插件。

脚本插件是一种普通的Gradle构建脚本,它可以完全访问Gradle的API。编写一个脚本插件是非常简单的,降低了分享代码的难度。可以通过URL被另一个项目使用。

对象插件通常包含更为复杂的逻辑,需要适当的包和类。每个对象插件的入口点都是Pulgin接口,它提供了一种直接的方式来访问Gradle的Project模型。通过将对象插件添加到构建脚本的classpath下,许多对象插件都可以在多个独立的项目中使用,可以被打包成jar文件,发布到仓库中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容