Skip to main content

groovy note

jvm上的动态语言, 语法类似Java, 过渡到 ruby

Groovy in Action》第二版

https://wizardforcel.gitbooks.io/ibm-j-pg/content/18.html

why groovy

场景

做规则引擎、流程引擎,可以做动态脚本环境,尤其是那些不需要发布又经常变更的场合

groovy有几个很强大的类GroovyClassLoader、GroovyScriptEngine,所以很适合用来做hotfix、hotswap、hotdeploy的东西

当胶水用的,比如连接各个Java组件,或者去实现一些易变性配置,比如用Groovy去写Maven插件,或者测试Java模块的测试例

优缺点

优点:

  • 减少代码,开发效率高,可以热加载

  • 对 集合, 正则, xml 支持非常好

  • 完全兼容Java语法, 能够和Java很好的结合, 语法简单多了

缺点:

  • 性能问题

    可以通过Groovy的静态编译 (通过添加注解@CompileStatic启用) 或者调用Java程序解决。

    如果静态编译, 会牺牲语言的动态特性

有哪些项目在使用

Gradle, 使用 groovy 语法构建项目, android 项目在用

Spock,测试框架,可通过其特有的DSL编写测试案例, 简单方便;

Grails,Web开发框架,无需自行编写脚手架代码,可用来快速开发;类比 Ruby on rails

Griffon,Swing开发框架,其灵感来自于Grails

jenkins dsl plugin ...通过groovy生成所有jenkins jobs 实现CICD as code

spring boot + groovy template 进行 web 开发 (https://www.logicbig.com/tutorials/spring-framework/spring-boot/groovy-view.html, https://www.ibm.com/developerworks/cn/java/j-pg02155/)

参考 http://www.groovy-lang.org/ecosystem.html

怎么使用

idea 环境搭建

idea 自带对 groovy 的编译支持, 智能提示, 但是运行还是要maven依赖的支持 (为了防止位置错误, 推荐 spring boot 集成)

通过 如下 按需引入:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
... 按需引入
</dependency>
</dependencies>

或者直接引入所有, 包括 json, xml 处理等等...:

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.2</version>
</dependency>

脚本位置: 可以在任意位置, 但是如果希望在 Java 中调用, 推荐在 class path 的 resources/groovy/script 下 新建 demo.groovy 脚本, 可以直接右键运行.

demo.groovy 中可以调用其他 groovy class (无论是否在 class path 下) , 也可以调用其他 Java class ,当然, 需要 import 对应类的包

groovy class 位置: 推荐在 /src/main/groovy 下 (标记位 class path), 这样有些插件默认配置无需修改. 命名一般大写, 如 GroovyPerson.groovy: (内部不要有其他脚本内容, 否则会类名冲突, 因为 groovy 代码若作为脚本运行, 会以脚本文件名生成一个 class, 这样就和脚本内定义的类冲突)

package io.github.xiaoyureed.starterdemo

/**
* @author xiaoyu* date: 2020/3/15
*/
class GroovyPerson {
String id
String name

String toString() {
return "${this.getClass().toString()}{${id}, ${name}}"
}
}

和 Java 的集成

http://groovy-lang.org/integrating.html

https://www.cnblogs.com/softidea/p/5122188.html

https://www.baeldung.com/groovy-java-applications, https://blog.csdn.net/qq_26886929/article/details/86501051

GroovyClassLoader

GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类 (将加载与脚本名称对应的类, 其余脚本内容无效)

//GroovyClassLoader与Java中的加载器一样,同一个类名的类只能加载一次,如果想再次加载,必须调 GroovyClassLoader的clearCache()方法移除所有已经加载的Groovy Class

public static GroovyObject groovyObject(String classpathSource) throws URISyntaxException, IOException, IllegalAccessException, InstantiationException {
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(
GroovyUtil.class.getClassLoader());
Class groovyClass = groovyClassLoader.parseClass(Paths.get(Thread.currentThread()
.getContextClassLoader().getResource(classpathSource).toURI()).toFile());
return (GroovyObject) groovyClass.newInstance();
}

@Test
public void t1() throws IOException, InstantiationException, IllegalAccessException, URISyntaxException {
GroovyObject groovyObject = GroovyUtil.groovyObject("groovy/clazz/Cat.groovy");
groovyObject.setProperty("id", UUID.randomUUID().toString());
groovyObject.setProperty("name", "xiao");
System.out.println(groovyObject);
groovyObject.invokeMethod("hi", null);
}

脚本内容:

package groovy.clazz

/**
* @author xiaoyu* date: 2020/3/16
*/
class Cat {
def name
def id
String toString() {
"${this.class.name}{ ${id}, ${name} }"
}

def hi() {
println "I am a cat [${name}]"
}
}

当然, 也可以直接执行脚本, 不过这不是 GroovyClassLoader 主要功能

GroovyShell

GroovyShell允许在Java类中求任意Groovy表达式的值。您可使用Binding对象输入参数给表达式, 并最终通过GroovyShell返回Groovy表达式的计算结果。

GroovyShell groovyShell = new GroovyShell();

优化:

private static GroovyShell groovyShell = new GroovyShell();

private static Map<String, Script> scriptCache = new ConcurrentHashMap<>();

private static <T> T invoke(String scriptText, String function, Object... objects) throws Exception {
Script script;
String cacheKey = DigestUtils.md5Hex(scriptText);

if (scriptCache.containsKey(cacheKey)) {
script = scriptCache.get(cacheKey);
} else {
script = groovyShell.parse(scriptText);
scriptCache.put(cacheKey, script);
}

return (T) InvokerHelper.invokeMethod(script, function, objects);
}

GroovyScriptEngine

GroovyScriptEngine从您指定的位置(文件系统,URL,数据库,等等, 即指定一个文件夹)加载Groovy脚本,并且随着脚本变化而重新加载它们

相互关联的多个脚本,使用GroovyScriptEngine会更好

@Test
public void t2() throws IOException, ResourceException, ScriptException {
// 若是数组, 则有多个脚本地址
// GroovyScriptEngine 可以作为一个全局唯一的 static 成员, 不必每次都创建
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(PathUtil.absoluteClasspath("groovy/script/"));
Binding binding = new Binding();
binding.setProperty("input", "xiao");
groovyScriptEngine.run("demo.groovy", binding);
}

/**
执行脚本的某个方法
*/
@Test
public void t2_1() throws javax.script.ScriptException, FileNotFoundException, NoSuchMethodException {
// GroovyScriptEngineFactory 可以作为 静态成员, 不必每次都创建
GroovyScriptEngineFactory groovyScriptEngineFactory = new GroovyScriptEngineFactory();
ScriptEngine scriptEngine = groovyScriptEngineFactory.getScriptEngine();
scriptEngine.eval(new FileReader(Paths.get(PathUtil.absoluteClasspath("groovy/script/demo1.groovy")).toFile()));
Invocable invocable = (Invocable) scriptEngine;
invocable.invokeFunction("func", "xiao");
}

脚本:

package groovy.script
/**
* @author xiaoyu* date: 2020/3/16
*/
println("demo =============")

def hi(str) {
println("hello, ${str}")
}

// input 为传入参数
hi(input)

优化

https://www.cnblogs.com/softidea/p/5122188.html

  • 使用GroovyShell的parse方法导致perm区爆满. 解决办法: 通常做法是缓存Script对象, key 为 groovyScript 脚本的md5值

    Groovy引擎每次执行脚本, 都会生成一个 class 和对应的 classloader, 无法被立即回收, 运行一段时间后将perm占满,一直触发fullgc

    为什么每次执行脚本都会生成新的 class? 因为对于同一个groovy脚本,groovy 引擎 为了保证每次执行的都是新的脚本内容,会每次生成一个新名字的Class文件 , 类名和时间戳有关

    为什么 groovy class loader 加载的类无法被 gc? 为了节省编译时间, GroovyClassLoader 代码中有一个 class 对象 的缓存 map, 脚本 class 对象被这个缓存引用, 无法回收

maven 中 Java 和 groovy 混合开发

maven 插件 及依赖:

默认会把src/main/javasrc/main/groovy作为源码目录

<project>
<build>
<plugins>
<!-- 增加 gmavenplus 插件 允许集成Groovy到Maven-->
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.7.1</version>
<executions>
<execution>
<goals>
<goal>addSources</goal>
<goal>addTestSources</goal>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>compileTests</goal>
<goal>removeStubs</goal>
<goal>removeTestStubs</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<!-- groovy-all 包含所有groovy GDK 中的包,
groovy 只包含基础 Groovy 编译需要的包-->
<artifactId>groovy-all</artifactId>
<!-- any version of Groovy \>= 1.8.2 should work here -->
<version>2.5.7</version>
<!--指定类型为 pom -->
<type>pom</type>
</dependency>
</dependencies>
</project>

如果 如果要使用其他目录作为Groovy源码目录, 比如src/additionalGroovy

<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.7.1</version>
<executions>
<execution>
<goals>
<goal>addSources</goal>
<goal>addTestSources</goal>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>compileTests</goal>
<goal>removeStubs</goal>
<goal>removeTestStubs</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<!-- 在此节点下配置源码目录,可配置多个 -->
<source>
<directory>${project.basedir}/src/main/groovy</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</source>
<source>
<directory>${project.basedir}/src/additionalGroovy</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</source>
</sources>
</configuration>
</plugin>
</plugins>
</build>

如果源码目录还是不生效, 将 src/main 整个作为 maven 源码目录:

<project>
<build>
<!-- 将${project.basedir}/src/main整个作为源码目录,你再添加什么源码都在里面了 -->
<sourceDirectory>${project.basedir}/src/main</sourceDirectory>
</build>
</project>

groovy 语法

http://www.groovy-lang.org/documentation.html

http://docs.groovy-lang.org/next/html/documentation/

所有类都是 public 的。所有属性都是 private 的 (每个属性自动提供一组 public getter 和 setter 方法),而所有方法都是 public

在脚本中调用 Java class 可以使用 new JavaPerson(firstName:"John", lastName:"Doe"), 当然, 调用 groovy class 也一样. 底层实现是 Groovy 先调用默认的无实参构造函数,然后调用每个字段的相应 setter

调用 getter 时, 无需使用更冗长的 gp.getFirstName(),只需调用 gp.firstName. 类似 的, gp.setLastName("Jones") 和 gp.lastName = "Jones" 是等效的

和 Java 的不同



// 默认导入 - default imports

/////////////////////////////////////

/**
* "==" 的不同 --- 在 Java中, 表示对象完全相等(地址, 内容全相同), groovy 中表示 equals; 如果希望取得Java中的效果, 使用 a.is(b)
*/

///////////////////////////////////////////////////////

/**
* default 必须位于 swith / case 结构的结尾 --- 在 Java 中,它可以放在 swith / case 结构中的任何位置,但在 Groovy 中,它更像是一个 else 子句,而非一个默认的 case 子句
*/

////////////////////////////////////////////////////

int ab=1//不需要分号

String multiLine='''
1//适应osc的markdwon
2//适应osc的markdwon
3'''//多行字符串

String interpolationString="$map or ${map}"//这是插值字符串,基本现代的语言都有吧

//////////////////////////////////////////////

/**
*
* 范围: 例如 “0..4” 表明包含 整数 0、1、2、3、4。Groovy 还支持排除范围,“0..<4” 表示 0、1、2、3。还可以创建字符范围:“a..e” 相当于 a、b、c、d、e。“a..<e” 包括小于 e 的所有值
*
* for(i in 0..5) {}
*
* def range=1..5//这是个range
*/

////////////////////////////////////////////////////////

/**
* 默认参数值: def repeat(val, repeat=5) {}, 那么repeat 是可选的
*/

////////////////////////////////////////////////////////


/**
* multi methods (方法重载) ------ dynamic binding
*/

int method(String str) {
return 1
}

int method(Object obj) {
return 2
}

Object o = "xxx"
def result = method(o)
println result // 1

///////////////////////////////////////////////

/**
* 在 Groovy 中可以省略 return 语句。Groovy 默认返回方法的最后一行
*/


///////////////////////////////////////////////


/**
* boxing/unboxing vs. widening ----- 在Java中 widening 优先于 boxing
*
* 但是在groovy 中, all primitive references use their wrapper class (基础类型自动使用包装对象)
*/

int i
m(i) // 下面哪个方法会调用?

void m(long l) { // java 中会调用, 在Java中 widening(延展) 优先于 boxing
println "in m(long)"
}

void m(Integer i) {// groovy 中会调用
println "in m(Integer)"
}


/////////////////////////////////////////////


/**
* magic method 魔法方法
*/
def numbers1 = [1,2,3,4]
assert numbers1.join(",") == "1,2,3,4"
assert [1,2,3,4,3].count(3) == 2

/////////////////////////////////////////////

/**
* - package scope ------ @PackageScope 包私有字段
*/
class Person implements Serializable {
@PackageScope
String name // 使用 @PackageScope 标注, 那么这个field是 package-private field

int age // 并不是 package-private field, 而是一个 private field, 以及 getter , setter

Person(name, age) {
this.name = name
this.age = age
}
}

////////////////////////////////////////////////

/**
* - array init -------- 使用 `[...]`, `{...}` 留给了closure
*/

int[] array = [1, 2, 3]

////////////////////////////////////////////////


/**
* 集合
*/
def map=[a:1,b:2]//这是个map
def map2 = [:] //默认是LinkedHashMap
assert map2.class == LinkedHashMap
map.'a' = 1 // key 包含包含空白字符之类的可以用这种方式
map.b = 2
map << [c: 3]
// null 空集合(包含map) 0 空字符串 空数组 在boolean环境中是false
def aMap=[:]
if (aMap){//groovy的false值,上面也提过
println('这段代码不会被执行')//aMap意味着false
}
map << ['c':3]//这是重载运算符,实现起来其实很简单

def list1=[1,2,3,4,5]//这是个list
def list2 = [] //默认是ArrayList
assert list2.class == ArrayList//可以省略.class

LinkedList list3 = []//因为默认是ArrayList,这种方式可以改变
assert list3.class == LinkedList

def numbers = [1,2,3,4]
assert numbers + 5 == [1,2,3,4,5]
assert numbers - [2,3] == [1,4] // 创建了新的集合实例

////////////////////////////////////////////////////////

/**
* 分布操作符 (spread operator) `*` -- 遍历集合
*/
assert ["JAVA", "GROOVY"] ==
["Java", "Groovy"]*.toUpperCase()


////////////////////////////////////////////////////////

/**
* 防止null 空指针异常 -- 在方法调用前面添加一个 ? 就相当于在调用前面放了一个条件,可以防止在 null 对象上调用方法。
*
*/
sng2.artist?.toUpperCase()


////////////////////////////////////////////////////////

/**
* 类初始化
*/
/**
* 对象的初始化: 默认提供构造函数, 默认提供 getter, setter
*/
def sng = new Song(name:"Le Freak", artist:"Chic", genre:"Disco")
/**
* class Pro {
* Integer property1 //1 没用private public protected(gorrvy里面是 @PackageScope)
* def property2 //2 可选的statoc final(有final就会没有set) 之类的
* final String property3 //3 def 或者 确定的类型
* static String property4 //4 property名字
* //满足以上四个条件就是一个property
*
* //实际上等于java以下内容,一个private+getter+setter(final修饰的没有setter)
* private String property5
*
* String getProperty5() {* return property5
* }
*
* void setProperty5(String property5) {
* this.property5 = property5
* }
* }
*
* static void main(String[] args) {*
* def pro = new Pro()
* Pro pro1 = [:]//也可以这样声明,前提是必须指定类型,并且有默认的构造器,以下等价
* Pro pro2 = []//等价以上
* Pro pro3 = [property1: 123]//等价以上
* def pro4 = [property1: 123] as Pro//等价以上
* pro.property1 = 123 //类似pro.setProperty1(123)
* println(pro.property1)//类似pro.getProperty1()
* }
*/

/////////////////////////////////////////////


/**
* ARM blocks (automatic resources management) ------- 类似Java中的 try () {}*/

// read file, demo1
new File("./package-info.java").eachLine("UTF-8") {
println it
}

// read file, demo2 ---- 更像Java的版本
new File("package-info.java").withReader('UTF-8') { reader ->
reader.eachLine {
println it
}
}

//////////////////////////////////////////////////


/**
* anonymous inner class 匿名内部类
*/

def called = new CountDownLatch(1)
def timer = new Timer()
timer.schedule(new TimerTask() {
@Override
void run() {
called.countDown()
}
}, 0)
//assert called.await(2, TimeUnit.SECONDS)

///////////////////////////////////////////////////


/**
* 闭包
*
* lambda expression ---- java8 中的 lambda 实际上是 anonymous inner class, groovy 中的才是真正的lambda -- closure(闭包)
*/

def acoll = ["Groovy", "Java", "Ruby"]

acoll.each{
println it // it 为关键字, 默认, 代表每个元素,可以修改
}
// 等价
acoll.each{ value -> // 自定义项变量
println value
}

// 怎么执行
def excite = { word ->
return "${word}!!"
}
assert "Groovy!!" == excite("Groovy")
assert "Java!!" == excite.call("Java")// 等价, 不常用


Runnable run = {
println 'run'
}
def list = Arrays.asList(1, 2, 3, 4)
list.each { println it }
list.each(this.&println)// 等价

////////////////////////////////////////////////////

/**
* GString - groovy string ---- "abc${1}de", 允许 `${...}`
*/

assert 'c'.getClass() == String
assert "c".getClass() == String // 等价
assert "c${1}".getClass() in GString

char a='a' // 单个字符的 string 会自动转型为 char
assert Character.digit(a, 16)==10 : 'But Groovy does boxing'
assert Character.digit((char) 'a', 16)==10
println 'assert ok'

// for single char strings, both are the same
assert ((char) "c").class==Character
assert ("c" as char).class==Character // 等价

// 对于包含多个字符的字符串来说,两种模式的结果并不相同
try {
((char) 'cx') == 'c'
assert false: 'will fail - not castable'
} catch(GroovyCastException e) {
println '多个字符不能转为char'
}
assert ('cx' as char) == 'c' // Groovy 模式的转换更宽容一些,只取第一个字符
assert 'cx'.asType(char) == 'c'// 等价
println 'assert ok'

//////////////////////////////////////////////////////

/**
* 静态编译 @CompileStatic 标注在类上; 但是每一个类都加@CompileStatic实在太麻烦,不过groovy提供了一个特性.自定义CompilerConfiguration
* 静态检查 @TypeChecked
*/

//////////////////////////////////////////////////////





输入输出流io

/**
* reading file
*/

def file = new File("./package-info.java")

void func1(File file) {
file.eachLine {line -> // 若 eachLine 中出现异常, resource 会自动关闭
println line
}
}

void func2(File file) {
file.eachLine {line, ln ->
println("Line $ln: " + line)
}
}

List func3(File file) {
return file.collect {it}
}

String[] func4(File file) {
return file as String[]
}

byte[] func5(File file) {
return file.bytes
}

void func6(File file) {
file.withInputStream {inputStream -> // stream 会自动关闭
new InputStreamReader(inputStream).eachLine {line ->
println(line)
}
}
}

//func1(file)
//func2(file)
//func3(file).forEach(System.out.&println)
//Stream.of(func4(file)).forEach(System.out.&println)
//func6(file)


/**
* write sth to a file
*/

void fun1(File file) {
file.withWriter('utf-8') {writer ->
writer.writeLine('// hello groovy')
}
}

// 原样输出 '''xxx'''
void fun2(File file) {
file << '''
// write sth new with `''`
'''
}

//fun1(file)
//fun2(file)

/**
* 对象数据的序列化反序列化
*/
// 序列化
void fun3(File file) {
def hello = 'hello'
def b = true

file.withDataOutputStream {out ->
out.writeBoolean(b)
out.writeUTF(hello)
}
}

// 反序列化
void fun4(File file) {
file.withDataInputStream {input ->
println(input.readBoolean()) // true
println(input.readUTF()) // hello
}
}

// 序列化/反序列化一个对象
void fun5(File file) {
def person = new Person('name', 10)

file.withObjectOutputStream {out ->
out.writeObject(person)
}

file.withObjectInputStream {input ->
def object = input.readObject()
println(object.name)
println(object.age)
}
}



//fun3(file)
//fun4(file)
//fun5(new File('d:/xiaoyu.txt'))


/**
* 遍历文件
*/

def dir = new File(".")

// 不会 recurse
void list1(File file) {
file.eachFile {f ->
println(f.name)
}
}

// 正则查找
void list2(File file) {
file.eachFileMatch(~/^package-info.java$/) { f ->
println(f.name)
}
}

// 会递归
void list3(File file) {
file.eachFileRecurse {f ->
println(f.name)
}
}

// 只针对 文件 执行闭包代码, 对于目录, 不执行 closure
void list4(File file) {
file.eachFileRecurse(FileType.FILES) {f ->
println(f.name)
}
}

void list5(File file) {
file.traverse {f ->
if (file.directory && file.name == 'bin') {
FileVisitResult.TERMINATE // 若遍历到名为 bin 的目录, 退出遍历
} else {
println(f.name)
FileVisitResult.CONTINUE // 继续遍历
}
}
}

//list1(dir)
//list2(dir)

/**
* 执行命令行命令
*/

void comm1() {
def process = 'ipconfig'.execute()
println(process.text) // 中文乱码
}

// Process 对象有 in/out/err 流, in 为标准输入流, 对应着command的输出结果; out 代表标准输出, 我们可以通过out发送数据给command
// "cmd /c dir" - Windows平台shell有些builtin command, 需要通过 cmd 调用
void comm2() {
def process = 'ipconfig'.execute()
process.in.eachLine('gbk') {line ->
println(line)// 使用 windows 默认 gbk 编码
}
}

// pipe line 管道
//todo
void comm3() {
proc1 = 'ipconfig'.execute()
proc2 = 'grep'
}

//comm1()
comm2()


有用的工具类

/**
* ConfigSlurper 用来处理 groovy script 格式的 config file
*/

void func1() {
def config = new ConfigSlurper().parse('''
app.date = new Date()
app.age = 10
app {
name = "Test${10}"
}
app."a.b" = "a.b hello"
''')

assert config.app.date instanceof Date
assert config.app.age == 10
assert config.app.name == 'Test10'
assert config.app.xxx != null // // 要么返回配置值,要么返回一个新的 ConfigObject 实例,但永远不会返回 null 值
assert config.app."a.b" == "a.b hello" // 单引号亦可
}

// 设置多环境, 简单起见, `environments` 不变
void func2() {

def config = new ConfigSlurper('dev').parse('''
environments {
dev {
app.port = 8080
}
prod {
app.post = 80
}
test {
app.port = 8081
}
}
''')
assert config.app.port == 8080
}

//`environments` 可以修改, 但是对应的也需要注册操作
void func3() {
def slurper = new ConfigSlurper()
slurper.registerConditionalBlock('myProject', 'developers')

def config = slurper.parse('''
sendMail = true

myProject {
developers {
sendMail = false
}
}
''')

assert config.sendMail == false
}

// 和 Java 的 properties 文件整合
void func4() {
def config = new ConfigSlurper().parse('''
app.date = new Date()
app.age = 42
app {
name = "Test${42}"
}
haha = "haha ${app.date}"
''')

def properties = config.toProperties()

println(properties."app.date") // Sat May 25 17:49:02 CST 2019
println(properties."haha") // haha Sat May 25 17:50:44 CST 2019

assert properties."app.date" instanceof String
assert properties."app.age" == '42'
assert properties."app.name" == 'Test42'
}

//func1()
//func2()
//func3()
//func4()


/**
* Expando - 动态可拓展对象
*/

void exp1() {
def expando = new Expando()
expando.name = 'name'
expando.someMethod = {s -> "some method. parameter = ${s}"}

println(expando.name)
println(expando.someMethod('aa'))

assert expando.name == 'name'
assert expando.someMethod('aa') == 'some method. parameter = aa'
}

//exp1()

/**
* Observable list, map and set 可观测集合, 列表
*/

元编程

package io.github.xiaoyu.groovydemo

import groovy.time.TimeCategory

/**
* MOP
* @author xiaoyu* @date 2019/5/25
*/
//class MetaProgram {
//}

/**
* 运行时元编程
* POJO - 普通 Java对象
* POGO - groovy 对象, 继承自 java.lang.Object 且默认实现了 groovy.lang.GroovyObject 接口。
* Groovy interceptor - Groovy 拦截器 —— 实现了 groovy.lang.GroovyInterceptable 接口的 Groovy 对象,并具有方法拦截功能
*/

/**
* GroovyObject 接口:
*
* public interface GroovyObject {
*
* // 除了具有 methodMissing的功能之外, 和 GroovyInterceptable 配合, 可以拦截所有方法, 开销大
* // 如果希望更通用的方式: 在一个对象的 MetaClass 上实现 invokeMethod()。该方法同时适于 POGO 与 POJO 对象
* Object invokeMethod(String methodName, Object methodArgs);
*
* // 每次读取 pogo 对象的field(无论是否存在)时, 都先调用这个方法
* Object getProperty(String propertyName);
*
* // 每次设置properties时调用
* void setProperty(String propertyName, Object newValue);
*
* MetaClass getMetaClass();
*
* void setMetaClass(MetaClass metaClass);
* }
*/

/**
* methodMissing - 拦截缺失的方法调用 (https://www.cnblogs.com/maijunjin/articles/3043935.html)
* 使用 methodMissing,并不会产生像调用 invokeMethod 那么大的开销,第二次调用代价也并不昂贵
*
* propertyMissing - 拦截缺失的属性调用
*/

class InvokeMethodDemo1 {
// override invokeMethod()
def invokeMethod(String method, Object args) {
"method ${method}(${args.join(', ')}) missing"
}
def test() {
'method test exist'
}
}

void func1() {
def demo = new InvokeMethodDemo1()
assert demo.test() == 'method test exist'
assert demo.xxx() == 'method xxx() missing'
assert demo.yyy('abc') == 'method yyy(abc) missing'
}

//func1()

class InvokeMethodDemo2 implements GroovyInterceptable {
def invokeMethod(String method, Object args) {
'hoho'
}
def test1() {
println 'test1'
}
}

void func2() {
def demo = new InvokeMethodDemo2()
assert demo.test1() == 'hoho'
assert demo.xxx() == 'hoho' // 调用不存在的方法, 也被拦截

// 如果想要拦截所有的方法调用,但又不想实现 GroovyInterceptable 这个接口,那
// 么可以在一个对象的 MetaClass 上实现 invokeMethod()
// 同时适于 POGO 与 POJO 对象
def str = 'aa'
str.metaClass.invokeMethod = {String method, Object args ->
"invoke method ${method}"
}
assert str.length() == 'invoke method length'
assert str.xxx() == 'invoke method xxx'
}
//func2()


class GetPropertyDemo {
def f1 = 'aa'
def f2 = 'bb'

def getProperty(String field) {
if (field == 'f3') { // 拦截对 f3 的读取
return 'f3'
}
// 其他的 field 放行
return metaClass.getProperty(this, field)
}

def getF4() {
'f4'
}

void setProperty(String name, Object value) {
this.@"$name" = value + '-tail'
}
}

void testGetPropertyDemo() {
def demo = new GetPropertyDemo()

assert demo.f1 == 'aa'
assert demo.f2 == 'bb'

assert demo.f3 == 'f3'
assert demo.f4 == 'f4'
}
//testGetPropertyDemo()

class GetSetAttrDemo{
def f1 = 'f1'
def f2 = 'f2'

def f3
def f4

}
void testGetSetAttrDemo() {
def demo = new GetSetAttrDemo()

assert demo.metaClass.getAttribute(demo, 'f1') == 'f1'
assert demo.metaClass.getAttribute(demo, 'f2') == 'f2'

demo.metaClass.setAttribute(demo, 'f3', 'f3')
demo.metaClass.setAttribute(demo, 'f4', 'f4')
assert demo.metaClass.getAttribute(demo, 'f3') == 'f3'
assert demo.metaClass.getAttribute(demo, 'f4') == 'f4'
}
//testGetSetAttrDemo()

class MethodMissingDemo {
def methodMissing(String name, def args) {
return "this is me"
}

// 针对静态方法调用的缺失
static def $static_methodMissing(String name, Object args) {
return "Missing static method name is $name"
}
}
void testMethodMissingDemo() {
def demo = new MethodMissingDemo()
assert demo.someMethod(20) == 'this is me'
assert MethodMissingDemo.xxx() == 'Missing static method name is xxx'
}
//testMethodMissingDemo()

class PropertyMissingDemo1 {
def propertyMissing(String f) { f }

// 针对静态field调用的缺失
static def $static_propertyMissing(String name) {
return "Missing static property name is $name"
}
}
void testPropertyMissingDemo1() {
assert new PropertyMissingDemo1().aaa == 'aaa'
assert PropertyMissingDemo1.xx == 'Missing static property name is xx'

}
//testPropertyMissingDemo1()

// 实现动态添加 field
class PropertyMissingDemo2 {
def storage = [:] // map

// 针对 setter
def propertyMissing(String name, value) { storage[name] = value }

// 针对 getter
def propertyMissing(String name) { storage[name] }
}

void testPropertyMissingDemo2() {
def demo2 = new PropertyMissingDemo2()
demo2.xxx = 'aaa'

assert demo2.xxx == 'aaa'
}
//testPropertyMissingDemo2()


/**
* Groovy 从 Objective-C 那里借用并实现了一个概念,叫做:类别(Categories)
*
* groovy 默认提供几个 categories class: TimeCategory, ServletCategory, DOMCategory
*
* categories class 中的方法均为static的
*
* 类别类默认是不能启用的。要想使用定义在类别类中的方法,必须要使用 GDK 所提供的 use(CategoriesClass, Closure)
*/
void testCategories() {
use(TimeCategory, {// closure 可以写到 小括号之外
println 1.minute.from.now // 一分钟之后 // TimeCategory 为 Integer 添加了方法
println(10.hour.ago)// 十小时之前

def date = new Date()
println(date - 3.months)// 三个月之前 // TimeCategory 为 Date 添加了方法
})
}

//testCategories()

/**
* 编译时元编程
*/


动态增加方法

//用闭包定义一个方法 var1为参数 ,->后面是执行语句(当然参数不是必须的)
def methodA={var1-> print "this is methodA"}

//用闭包定义一个方法 var1为参数 ,->后面是执行语句(当然参数不是必须的)
def methodB={var1-> print "this is methodB"}

String.metaClass.addMethodA=methodA; //将methodA绑定为成员方法。
String.metaClass.'static'.addMethodB=methodB; //将methodB绑定为静态方法

String s="str";
s.addMethodA('good'); //实例调用方法A
String.addMethodB('hello'); //静态类调用方法B

底层运行原理

groovy.lang.GroovyObject (接口): groovy class 会默认实现这个接口

groovy.lang.Script (抽象类): groovyc 编译器将获取源文件的名称并创建了一个同名的类继承 Script, 整个脚本中 非 groovy class 代码会编译到这里

Groovy代码文件与class文件的对应关系

Groovy 允许完全省略编译步骤,不过仍然可以 进行编译。如果想要编译代码,可以使用 Groovy 编译器 groovyc。用 groovyc 编译 Groovy 代码会产生标准的 Java 字节码

  • 如果Groovy脚本文件里只有执行代码,没有定义任何类(class)

    则编译器会生成一个Script的子类,类名和脚本文件的文件名一样,而脚本的代码会被包含在一个名为run的方法中,同时还会生成一个main方法,作为整个脚本的入口。

  • 如果Groovy脚本文件里仅含有一个类,而这个类的名字又和脚本文件的名字一致

    这种情况下就和Java是一样的,即生成与所定义的类一致的class文件。

  • 如果Groovy脚本文件含有多个类,

    groovy编译器会为每个类生成一个对应的class文件。如果想直接执行这个脚本,则脚本里的第一个类必须有一个static的main方法。