本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!

1、前言

Gradle是一个构建东西,面向开发者的脚本言语是GroovyKotlin,即咱们常用的build.gradle和build.gradle.kts或plugin等。

那么在Gradle 5.0之后已经支撑Kotlin的情况下,为什么还要讲Groovy,直接上Kotlin不行吗?

先来看一个图:

【Gradle-3】Gradle中的DSL,Groovy & Kotlin

这是Gradle使用的编程言语占比,排在榜首的是Groovy,虽然有一部分是测验代码,但也说明groovy仍是主流,

其次,在咱们新建项目的时分,Groovy依然是默许的构建脚本言语;而且,截至现在依然有许多公司许多项目并没有迁移到Kotlin。所以在当下,Groovy依然是Gradle不得不提的官方构建脚本言语

在gradle中,有大量的装备是经过脚本言语来编写的,不管是Groovy仍是Kotlin,最终的体现都是DSL,所以抛开编程言语不讲,DSL你也是逃不掉的。

本文首要介绍的内容:

  1. 什么是DSL;
  2. Groovy DSL & Kotlin DSL;
  3. Groovy 根底语法;

2、什么是DSL

DSL全称:Domain Specific Language,即范畴特定言语,它是编程言语赋予开发者的一种特殊才能,经过它咱们能够编写出一些看似脱离其原始语法结构的代码,然后构建出一种专有的语法结构。

DSL分为两类,外部DSL和内部DSL。

2.1、外部DSL

也称独立DSL。由于它们是从零开始树立起来的独立言语,而不根据任何现有宿主言语的设备树立。外部DSL是从零开发的DSL,在词法剖析、解析技能、解释、编译、代码生成等方面具有独立的设备。开发外部DSL近似于从零开始完成一种具有共同语法和语义的全新言语。构建东西make 、语法剖析器生成东西YACC、词法剖析东西LEX等都是常见的外部DSL。例如:正则表达式、XML、SQL、JSON、 Markdown等;而大厂一般自研的动态化计划便是根据外部DSL来做的,从上传下发,到端侧解析烘托,然后完成一种不依靠发版且根据原生的动态化计划。

2.2、内部DSL

也称内嵌式DSL。由于它们的完成嵌入到宿主言语中,与之合为一体。内部DSL将一种现有编程言语作为宿主言语,根据其设备树立专门面向特定范畴的各种语义。例如:Groovy DSL、Kotlin DSL等;简而言之能够了解为,这种DSL简化了原有的语法结构(看起来有点像lambda),这种简化大大的进步了简练性,但也增加了了解本钱,Gradle中的许多装备都是如此,这也是Gradle上手难的原因之一。

3、Gradle中的DSL

3.1、Groovy DSL

新建项目时Gradle默许的是Groovy言语,也即Groovy DSL,比方app>build.gradle:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}
android {
    namespace 'com.yechaoa.gradlex'
    compileSdk 32
    defaultConfig {
        applicationId "com.yechaoa.gradlex"
        minSdk 23
        targetSdk 32
        versionCode 1
        versionName "1.0"
    }
}
dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
}

咱们来看榜首段代码:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

这是一段用于声明引入Gradle插件plugin的DSL代码。

上面的plugin声明是简写版,完整版是这样的:

plugins {
    id 'com.android.application' version '7.3.0' apply false
}

3.1.1、id

调用的是PluginDependenciesSpec中的id(String id)函数,返回PluginDependencySpec目标,PluginDependencySpec目标能够了解为是PluginDependenciesSpec的一层封装,比方id(String id)函数只要一个参数,那versionapply哪里来的呢,便是在PluginDependencySpec目标里的。

留意:PluginDependenciesSpec和PluginDependencySpec长得像,但不相同的。

3.1.2、version

插件版别号。Gradle默许插件是不需要指定版别号的,但是三方的插件则有必要指定版别号。很好了解,自带插件是能够跟着Gradle版别一起发布的,三方插件则是发布到远端库房的,比方Gradle库房gradlePluginPortal(),plugins.gradle.org。

3.1.3、apply

是否将插件应用于当时项目及子项目,默许是true

其实apply关键字便是用于办理依靠传递的。当咱们不想传递时,就能够设置apply false

除此之外,还能够用表达式来办理依靠传递,比方这么写:

subprojects {
    if (isNeedApply) {
        apply plugin: "org.company.myplugin"
    }
}

3.2、复原DSL

介绍完plugin的声明之后,再回到plugins这段DSL代码

plugins {
    id 'com.android.application'
}

咱们能够了解plugins是一个函数,plugins { } 里边接纳的是一个闭包Closure,也能够这么写:

plugins ({
    id 'com.android.application'
})

在Groovy中,当函数的最终一个参数或许只要一个参数是闭包时,是能够写在参数括号外面的,所以还能够继续复原:

plugins () {
    id 'com.android.application'
}

在Groovy中,当函数只要一个参数的时分,是能够省掉括号的,所以就回到了咱们最初的DSL代码:

plugins {
    id 'com.android.application'
}

3.3、Kotlin写法

仍是以plugins为例,来看看Kotlin是怎么写的。

简写版:

plugins {
//    id 'com.android.application'
    id("com.android.application")
}

完整版:

plugins {
//    id 'com.android.application' version '7.3.0' apply false
    id("com.android.application") version "7.3.0" apply false
}

咱们能够看出来,其实Kotlin DSLGroovy DSL的写法不同不大,也是调用PluginDependenciesSpec中的id(String id)函数,只不过不同在调用id(String id)函数时有显式的括号罢了。

4、闭包

上面的复原DSL其实涉及到Groovy里边一个十分重要的概念,闭包(Closure)。

闭包的展现方法跟Kotlin里边的lambda不能说十分相似吧,只能说是一毛相同。(kt吸收了许多精华)

4.1、界说闭包

咱们先简略界说一个闭包:

// Closure
def myClosure = {}
println(myClosure)

{ } 这个便是闭包,里边是空的什么都没做,所以打印出来什么也没有。

这个闭包目标能够了解为是一个函数,函数是能够接纳参数的,咱们加个参数改造一下:

// Closure
def myClosure = { param ->
    param + 1
}
println(myClosure(1))

加了一个param参数,并在闭包里履行 +1 操作,然后打印这个闭包的时分传参为 1 。

输出:

> // Closure
> def myClosure = { param ->
>     param + 1
> }
> println(myClosure(1))
2

打印结果:2。

4.2、Kotlin DSL

看到这儿,会Kotlin的同学肯定有感受,这跟Kotlin函数参数几乎太像了。

咱们看一下Kotlin的函数参数:

private fun setPrintln(doPrintln: (String) -> Unit) {
    doPrintln.invoke("yechaoa")
}
fun main() {
    setPrintln { param ->
        println(param)
    }
}
  1. 界说了一个高阶函数setPrintln,参数doPrintln是一个函数参数,String表明函数参数doPrintln的参数类型,Unit表明doPrintln不需要返回值。
  2. 然后doPrintln履行并传参”yechaoa”。

setPrintln函数调用实际上是这样的:

    setPrintln {
    }

setPrintln函数的这个{ },跟上面的闭包一毛相同,就也称为闭包吧(lambda,其实也是DSL语法)。

然后咱们在Kotlin的这个闭包里履行了一行代码:

println(param)

这个param便是doPrintln函数传的”yechaoa”参数。

然后咱们看下输出:

yechaoa
Process finished with exit code 0

4.3、Groovy 闭包参数

比照完,咱们再次回到Groovy的闭包。

上面咱们界说了一个参数param,其实一个参数界说的时分是能够省掉显现界说的,用it表明:

// Closure
def myClosure = {
    it + 1
}
println(myClosure(1))

顺便来看下两个参数的:

// Closure
def myClosure = { param, param2 ->
    param + 1 + param2
}
println(myClosure(1, 1))

输出:

> // Closure
> def myClosure = { param, param2 ->
>     param + 1 + param2
> }
> println(myClosure(1, 1))
3

其实跟一个参数的方法没不同,跟Kotlin的写法也相同。

4.4、闭包函数参数

来看下当闭包作为函数参数时是怎样的体现:

def myClosure(Closure closure) {
    println closure()
}
myClosure {
    "yechaoa"
}
  1. 界说了一个myClosure函数,参数是一个闭包,然后在函数里边对闭包进行打印;
  2. 直接调用这个函数,在闭包 { } 里传了一个字符串”yechaoa”。

咱们这儿的打印是println closure(),而不是println(closure())了,在Groovy DSL中,不发生歧义的情况下是能够去掉括号的,这也是咱们前面说到的Groovy DSL和Kotlin DSL的差异,即id ‘com.android.application’和id(“com.android.application”)的不同。

好了,看下输出:

> def myClosure(Closure closure) {
>     println closure()
> }
> 
> myClosure {
>     "yechaoa"
> }
yechaoa

到这儿,有没有发现,这儿的myClosure { }是不是跟plugins { }很像,不对,几乎一毛相同。

咱们在前文(【Gradle-2】一文搞懂Gradle装备)中的源码剖析部分也有说到,Gradle中的装备块其实都是闭包调用。

myClosure { }就好像plugins { },”yechaoa”好像plugins { }里边的装备,myClosure(Closure closure)函数里边能够获取到”yechaoa”并打印出来,Gradle Project目标相同能够获取到plugins { }里边依靠的插件,然后解析履行编译构建。

经过闭包演示,是不是对Gradle的DSL了解轻松许多了~(别跑,点个赞)

以上Groovy演示代码都能够在 IDEA>Tools>Groovy Console 里履行测验。

5、Groovy

5.1、简介

Groovy是Apache 旗下的一种强壮的、可选类型的和动态的言语,具有静态类型和静态编译功用,用于Java平台,旨在经过简练、了解和易于学习的语法进步开发人员的生产力。它与任何Java程序顺利集成,并立即为您的应用程序供给强壮的功用,包含脚本功用、范畴特定言语创造、运行时和编译时元编程以及函数编程。

而且,Groovy能够与Java言语无缝联接,能够在Groovy中直接写Java代码,也能够在Java中直接调用Groovy脚本,十分丝滑,学习本钱很低。

5.2、根底语法

5.2.1、注释

注释和 Java 相同,支撑单行//、多行/**/和文档注释/** */

不同的是Groovy在脚本里的注释用#

5.2.2、关键字

as、assertbreakcasecatch、class、const、continue、def、defaultdoelseenum、extends、falsefinallyfor、goto、if、implements、
import、in、instanceof、interface、newnullpackagereturnsuperswitchthisthrowthrows、trait、truetrywhile
asin、permitsrecord、sealed、trait、var、yields
nulltruefalsebooleancharbyteshortintlongfloatdouble

5.2.3、字符串

在Groovy种有两种字符串类型,一般字符串java.lang.String和插值字符串groovy.lang.GString

一般字符串:

println 'yechaoa'

插值字符串:

def name = 'yechaoa'
println "hello ${name}"
// or
println "hello $name"

5.2.4、数字

与java类型相同:

  • byte(8位)
  • char(16位,可用作数字类型,表明UTF-16代码)
  • short(16位)
  • int(32位)
  • long(64位)
  • java.math.BigInteger(2147483647个int)

能够这么声明:

byte  b = 1
char  c = 2
short s = 3
int   i = 4
long  l = 5
BigInteger bi =  6

类型会根据数字的大小调整。

def a = 1
assert a instanceof Integer
// Integer.MAX_VALUE
def b = 2147483647
assert b instanceof Integer
// Integer.MAX_VALUE + 1
def c = 2147483648
assert c instanceof Long
// Long.MAX_VALUE
def d = 9223372036854775807
assert d instanceof Long
// Long.MAX_VALUE + 1
def e = 9223372036854775808
assert e instanceof BigInteger

5.2.5、变量

Groovy供给了def关键字,跟Kotlin的var相同,归于类型推导。

def a = 123
def b = 'b'
def c = true 
boolean d = false
int e = 123

但Groovy其实是强类型言语,但是与Kotlin相同,也支撑类型推导。

下面这几种界说也都是ok的

def a1 = "yechaoa"
String a2 = "yechaoa"
a3 = "yechaoa"
println(a3)

5.2.6、运算符

算术运算符

assert  1  + 2 == 3
assert  4  - 3 == 1
assert  3  * 5 == 15
assert  3  / 2 == 1.5
assert 10  % 3 == 1
assert  2 ** 3 == 8

一元运算符

def a = 2
def b = a++ * 3             
assert a == 3 && b == 6
def c = 3
def d = c-- * 2             
assert c == 2 && d == 6
def e = 1
def f = ++e + 3             
assert e == 2 && f == 5
def g = 4
def h = --g + 1             
assert g == 3 && h == 4

赋值运算符

def a = 4
a += 3
assert a == 7
def b = 5
b -= 3
assert b == 2
def c = 5
c *= 3
assert c == 15
def d = 10
d /= 2
assert d == 5
def e = 10
e %= 3
assert e == 1
def f = 3
f **= 2
assert f == 9

关系运算符

assert 1 + 2 == 3
assert 3 != 4
assert -2 < 3
assert 2 <= 2
assert 3 <= 4
assert 5 > 1
assert 5 >= -2

逻辑运算符

assert !false
assert true && true     
assert true || false  

位移运算符

assert 12.equals(3 << 2)
assert 24L.equals(3L << 3)         
assert 48G.equals(3G << 4)         
assert 4095 == -200 >>> 20
assert -1 == -200 >> 20
assert 2G == 5G >> 1
assert -3G == -5G >> 1

5.2.7、List

Groovy用大括号表明列表,参数用逗号切割。

def numbers = [1, 2, 3]
assert numbers instanceof List  
assert numbers.size() == 3  

除了能够界说同类型之外,也能够界说不同类型值的列表

def heterogeneous = [1, "a", true]

迭代经过调用eacheachWithIndex方法,与Kotlin相似

[1, 2, 3].each {
    println "Item: $it"//it是对应于当时元素的隐式参数
}
['a', 'b', 'c'].eachWithIndex { it, i -> //it是当时元素, i是索引方位
    println "$i: $it"
}

5.2.8、Arrays

跟List差不多,不相同的是 需要显现声明类型

String[] arrStr = ['Ananas', 'Banana', 'Kiwi']
assert arrStr instanceof String[]    
assert !(arrStr instanceof List)
def numArr = [1, 2, 3] as int[]      
assert numArr instanceof int[]       
assert numArr.size() == 3

5.2.9、Map

key-value映射,每一对键值用冒号:表明,对与对之间用逗号切割,最外层是中括号。

def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
assert colors['red'] == '#FF0000'    
assert colors.green  == '#00FF00'    
colors['pink'] = '#FF00FF'           
colors.yellow  = '#FFFF00'           
assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'
assert colors instanceof java.util.LinkedHashMap

5.2.10、闭包

Groovy 供给了闭包的支撑,语法和 Lambda 表达式有些相似,简略来说便是一段可履行的代码块或函数指针。闭包在 Groovy 中是groovy.lang.Closure类的实例,这使得闭包能够赋值给变量,或许作为参数传递。

Groovy 界说闭包的语法很简略:

{ [closureParameters -> ] statements }

尽管闭包是代码块,但它能够作为任何其他变量分配给变量或字段:

def listener = { e -> println "Clicked on $e.source" }
assert listener instanceof Closure
Closure callback = { println 'Done!' }                      
Closure<Boolean> isTextFile = {
    File it -> it.name.endsWith('.txt')                     
}

闭包能够拜访外部变量,而函数则不能。

def str = 'yechaoa'
def closure={
    println str
}
closure()//yechaoa 

闭包调用的方法有两种,闭包.call(参数)或许闭包(参数),在调用的时分能够省掉圆括号。

def closure = {
    param -> println param
}
closure('yechaoa')
closure.call('yechaoa')
closure 'yechaoa'

闭包的参数是可选的,假如没有参数的话能够省掉->操作符。

def closure = {println 'yechaoa'}
closure()

多个参数以逗号分隔,参数类型和函数相同能够显式声明也可省掉。

def closure = { String x, int y ->
    println "hey ${x} the value is ${y}"
}

假如只要一个参数的话,也可省掉参数的界说,Groovy供给了一个隐式的参数it来代替它。相似Kotlin。

def closure = { it -> println it }
//和上面是等价的
def closure = { println it }   
closure('yechaoa')

闭包能够作为参数传入,闭包作为函数的仅有参数或最终一个参数时可省掉括号。

def eachLine(lines, closure) {
    for (String line : lines) {
        closure(line)
    }
}
eachLine('a'..'z',{ println it }) 
//可省掉括号,与上面等价
eachLine('a'..'z') { println it }

相似kotlin的高阶函数。

再举个常见的比如,咱们常常用到的dependencies

dependencies {
    testImplementation 'junit:junit:4.13.2'
}

这个dependencies便是一个闭包,等价于

dependencies ({
    testImplementation 'junit:junit:4.13.2'
})

继续等价于

dependencies ({
    testImplementation('junit:junit:4.13.2')
})

没错,testImplementation也是闭包,所以现在咱们在kotlin中也能够常常见到implementation(‘xxx’)这种写法。

5.2.11、IO操作

5.2.11.1、读文件

读取文本文件并打印每一行文本

new File(baseDir, 'yechaoa.txt').eachLine{ line ->
    println line
}

InputStream

new File(baseDir,'yechaoa.txt').withInputStream { stream ->
    // do something ...
}

5.2.11.1、写文件

new File(baseDir,'yechaoa.txt').withWriter('utf-8') { writer ->
    writer.writeLine 'Into the ancient pond'
    writer.writeLine 'A frog jumps'
    writer.writeLine 'Water’s sound!'
}

OutputStream

new File(baseDir,'data.bin').withOutputStream { stream ->
    // do something ...
}

5.2.12、小结

Groovy整体语法特性与java、kotlin都有相似之处,比较简略上手。

当然,也能够用kotlin开发,kotlin相比groovy在开发上也有不少优势,比代码补全、支撑跳转、显现注释等。

从groovy迁移到kotlin能够参考两篇官方的文档:

  • Migrating build logic from Groovy to Kotlin
  • 将 build 装备从 Groovy 迁移到 KTS

6、总结

本文先是介绍了什么是DSL,然后比照了Groovy DSL 和 Kotlin DSL语法上的一些差异,以及闭包的解说,最终简略介绍了一下Groovy的根底语法。核心是DSL和闭包的概念,希望本文对你有所协助~

写作不易,点个赞吧~

7、相关文档

  • DSL 范畴特定言语
  • 《榜首行代码》
  • Gradle Build Language Reference
  • Groovy官方文档
  • Domain-Specific Languages
  • Groovy in Action, Second Edition
  • Groovy 言语快速入门
  • Gradle入门教程
  • Migrating build logic from Groovy to Kotlin
  • 将 build 装备从 Groovy 迁移到 KTS