Groovy是一门很灵活的Java扩展语言,支持弱类型、闭包、函数式编程等脚本语言的高级特性。因为小卷所在公司的船申报系统需要重构,对原先java
硬编码的各种表单数据校验、后台业务校验使用规则脚本的形式进行剥离出来。而市面上像Jboss Drools
这样的规则引擎又感觉太重了,所以选择更轻量级也更容易在java
体系中整合的groovy
,为此,开始新一轮的groovy dsl
学习之旅。
因为groovy dsl
的脚本形式有非常好的可读性,且只要有jvm
虚拟机环境就可以独立或者嵌入到当前java
模块中运行,这就是gradle
作为项目构建工具非常受欢迎的原因,因为它提供了高可读、高可维护性、高灵活度的dsl
软件构建脚本。
如果你跟小卷一样做全栈开发,体验过前端从js
到ts
的严谨,那你一定也想体验下后端从java
到groovy
的松散洒脱。咱们一起开始groovy
学习之旅吧。
学习参考
万变不离其宗的官方参考文档
csdn优质创作者猿泰山 博客:
PACKT出版社出版的:Groovy for Domain-specific Languages - 第二版英文版 (Dearle, Fergal)
说明
这本英文实战书是小卷学习的主要参考,里面涉及到
groovy dsl
语言特性的更多实战。在后续的博文分享中也会体现出来。
工程搭建
语法特性初体验
类型由运行时决定
def myVar = 'hello'
println myVar.class
myVar = 123.4
println myVar.class
Java里的类型是编译时就确定了,而groovy
中则在运行时才被确定,这里def
也可以用String
声明,这样在赋值其他类型时存在一个隐式转换。
练一练
将上面代码加到
com/juan/groovy/Main.groovy
的main
方法中,先用def
声明,再改成String
,看输出。后续练习若无说明,默认也在Main.groovy
的main
方法中编写。
list和map操作
// list定义与访问
def students = ['小张', '小李', '小王']
println students
println students[1]
// map定义与访问
def lili = [name: '莉莉', age: 22]
println lili
println lili.name
println lili['age']
定义和访问方式跟js
类似,注意这里对象字面量的定义形式用[ ... ]
而不是{ ... }
闭包 - 声明与执行分离
// 闭包可以当作一个有入参和返回值的函数
def min = { num1, num2 -> Math.min(num1, num2) }
// 通过call来调用
def result = min.call(2, 3)
println result
// 直接用函数变量名调用
result = min(2, 3)
println result
// 调用时括号可以省略
result = min 2, 3
println result
现在咱们姑且先把闭包当作一个函数来使用,注意,闭包中定义的内容是用{ ... }
进行关闭和隔离的。再来看一个实现打印的小栗子:
// 实现一个打印的闭包
def myPrint = { a -> print a }
myPrint ('hello\n')
def myPrint2 = { a, b -> print a; print b }
myPrint2 ('hello', 'world\n')
groovy
语法的魅力,化繁为简,语法可以精简到如下程度:
def myPrint = { print it } // 一个参数时可以使用隐式变量it
myPrint 'hello\n' // 括号可以省略
def myPrint2 = { a, b -> print a print b } // 这里的分号也可以省略
myPrint2 'hello', 'world\n' // 括号可以省略
什么时候不能省略分号
一般的执行语句都可以省略
;
,当然也有一些特殊情况,比如:def a = 1; def b = 2 // 两个def的声明语句中间的;不能省略 print a print b // 可省略;
再来看个求最小的小练习来巩固下闭包函数的用法:
// 定义求最小值的闭包
// 箭头函数体内的语句无需分号,最后一个语句为返回值
def min = { list ->
def min = list[0]
list.each { n -> if (n < min) min = n }
min
}
def list = [ 5, 2, 3, 1, 4, 6]
println min(list) // 这里min的括号调用不可省略
println( min list ) // 这种形式也是可以的
操作符重载
groovy
仿照C++实现语言基本特性的操作符重载,可以扩展类中相应的方法来实现操作符的重载,比如plus()
方法,不同的类型提供的不同的plus()
方法的重载来实现+
操作符,操作数的类型不同,实现也不一样,看例子:
def str = 'hello '
str = str.plus('groovy')
println str
def d = 123.4
d = d.plus(12) // 如果是'12'呢,尝试改下
println d
正则
比如实现一个字符串多个连续空格替换为一个:
def str = ' aa bbb cc '
// =~ 操作符表示对目标字符串应用正则模式,执行结果为一个匹配器,这里是匹配多个连续空格
def matcher = str =~ ' +'
def result = matcher.replaceAll(' ')
println "|$result|"
// 字符串模板中的表达式写法,注意,外面要用双引号包裹
println "|${result + '!!!'}|"
标记语言构建器
需要引入依赖:
implementation 'org.apache.groovy:groovy-xml:4.0.14'
这个groovy-xml
充分实现了dsl给标记语言的声明所带来的可读性和可维护性,比写原生标记语言要强太多
def writer = new StringWriter()
def builder = new MarkupBuilder(writer)
builder.course {
author {
name '小卷'
}
title '跟着小卷学groovy'
price 0 // 完全免费
}
println writer
这里通过groovy-xml
扩展模块的标记语言构建器,可以很轻松的对groovy
的对象声明的简洁语法构建出xml
格式的数据,以满足远程服务调用所需的格式,非常简单!得到的输出结果:
<course>
<author>
<name>小卷</name>
</author>
<title>跟着小卷学groovy</title>
<price>0</price>
</course>
关于类型导入
一般的常用类型、内置类型,在
groovy
代码中都不需要导入;而对于第三方的模块中的类型,需要导入其类型。