初识 Groovy
简介
Groovy
由James Strachan于2003年创建,它的目标是提供一个与Java紧密集成、更灵活、更简洁的编程语言,成为一种功能丰富且对 Java 友好的语言,将动态语言的优势引入一个强大且支持良好的平台。
作为一种运行在Java虚拟机上的脚本语言,Groovy以其动态性和与Java语言的高度集成而著称。它支持动态类型、闭包、简洁语法等特性,使开发者能够更高效地编写代码。在总体上,Java非常适合用于工具、库和基础设施,而Groovy则非常适合于几乎所有其他用途。
与Java的关系:
-
兼容性:Groovy与Java高度兼容,可以直接访问Java类和库。
-
互操作性:Java可以调用Groovy代码,反之亦然。
主要语法特性:
- 动态类型。
- 结束不需要分号。
- 可选 return 关键字,默认最后一个变量作为返回值。
应用场景:
- 脚本编写
- 测试框架
- Gradle构建工具
- Web开发
- DSL(领域设计语言)
第一个程序
- 通过 Maven引入;或者,通过安装 Groovy 的二进制发行版:https://groovy.apache.org/download.html
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.5.14</version>
</dependency>
- 编写 Demo.groovy
// Demo.groovy
class Demo {
static void main(String[] args) {
// 打印 hello world!
println "hello world!"
}
}
语法对比
- 动态类型。与 Java 相比,Java 是一种"强"类型语言,编译器知道每个变量的所有类型,并且可以在编译时理解和遵守合同。在 Groovy 中,可选类型是通过
def
关键字完成的。
// Example.groovy
class Example {
static void main(String[] args) {
// 使用 def 的整数示例
def aint = 100;
println(aint);
}
}
- true 的判断。所有 null、void、等于零或 empty 的对象都计算为 false
if (name != null && name.length > 0) {}
// 等价于
if (name) {}
- == 的作用。在 Groovy 中,
==
意味着所有地方都相等。对于非基元,当评估Comparable对象的相等性时,它会转换为a.compareTo(b) == 0
,否则转换为a.equals(b)
。要检查同一性(引用相等),请使用is
方法:a.is(b)
。
status != null && status.equals(ControlConstants.STATUS_COMPLETED)
// 等价于
status == ControlConstants.STATUS_COMPLETED
- 有用的语法糖。
?.
安全航导运算符?:
Elvis 运算符;
// Groovy引入了安全导航运算符 ?.,可以避免空指针异常的发生。
if (order != null) {
if (order.getCustomer() != null) {
if (order.getCustomer().getAddress() != null) {
System.out.println(order.getCustomer().getAddress());
}
}
}
// 等价于
println order?.customer?.address
// Elvis 运算符是一种特殊的三元运算符快捷方式,可以方便地用于默认值。
def result = name != null ? name : "Unknown"
// 等价于
def result = name ?: "Unknown"
- getter 和 setter。在 Groovy 中,getter 和 setter 构成了我们所说的“属性”,并提供了访问和设置此类属性的快捷方式表示法;在 Groovy 中编写 bean时,您不必自己创建字段和 getter / setter,而是让 Groovy 编译器为您完成。
// 在 Groovy 中,getter 和 setter 构成了我们所说的“属性”,并提供了访问和设置此类属性的快捷方式表示法
resourcePrototype.setName("something")
// 等价于
resourcePrototype.name = "something"
class Person {
private String name
String getName() { return name }
void setName(String name) { this.name = name }
}
// 等价于
class Person {
String name
}
- 使用
with()
和tap()
对同一个bean 进行重复操作。
server.name = application.name
server.status = status
server.sessionCount = 3
server.start()
server.stop()
// 等价于
server.with {
name = application.name
status = status
sessionCount = 3
start()
stop() // 闭包中,最后一条语句视为返回值。
}
def person = new Person().with {
name = "Ada Lovelace"
it // 返回本身
}
// 等价于
def person = new Person().tap {
name = "Ada Lovelace"
}
注意:您还可以使用 with(true) 代替 tap(),使用 with(false) 代替 with()。
- 其他。
- 对比 Java详细说明:https://www.groovy-lang.org/differences.html
- 语法特性详细说明:https://www.groovy-lang.org/style-guide.html
简单应用
使用如下Groovy类库,从Java中调用Groovy脚本:
- Eval:最简单集成的方法
- GroovyShell:运行脚本的首选方法
- Script:自定义脚本类
- GroovyClassLoader:Groovy类加载器
- GroovyScriptEngine:Groovy脚本引擎
此外,还可以使用 JSR 223,从Java中调用Groovy脚本。
Eval、GroovyShell
groovy.util.Eval
类是在运行时动态执行 Groovy 的最简单方法。这可以通过调用 me
方法来完成:
// Groovy 默认导入包:groovy.util.*
// import groovy.util.Eval
class EvalDemo {
static void main(String[] args) {
assert Eval.me("33*3") == 99
}
}
Eval
支持多种接受参数进行简单判定的变体:
class EvalDemo {
static void main(String[] args) {
// 使用名为x绑定参数进行简单判定
assert Eval.x(4, '2*x') == 8
// 相同的判定,使用名为k自定义绑定参数
assert Eval.me('k', 4, '2*k') == 8
// 使用名为x和y两个绑定参数进行简单判定
assert Eval.xy(4, 5, 'x*y') == 20
// 使用名为x 、 y和z三个绑定参数进行简单判定
assert Eval.xyz(4, 5, 6, 'x*y+z') == 26
}
}
groovy.lang.GroovyShell
类是执行脚本的首选方法,并且能够缓存生成的脚本实例。虽然Eval
类返回编译脚本的执行结果,但GroovyShell
类提供了更多选项。
// GroovyShellDemo.groovy
class GroovyShellDemo {
static void main(String[] args) {
// 创建一个新的GroovyShell实例
def shell = new GroovyShell()
// 可以用作直接执行代码的Eval
def result = shell.evaluate '3*5'
def result2 = shell.evaluate(new StringReader('3*5'))
assert result == result2
def script = shell.parse '3*5'
assert script instanceof groovy.lang.Script
assert script.run() == 15
}
}
等价于Java代码
import groovy.lang.Script;
import java.io.StringReader;
public class GroovyShellDemo {
public static void main(String[] args) {
// 创建一个新的GroovyShell实例
GroovyShell shell = new GroovyShell();
// 可以用作直接执行代码的Eval
Object result = shell.evaluate( "3*5");
Object result2 = shell.evaluate(new StringReader("3*5"));
assert result == result2;
Script script = shell.parse("3*5");
assert script instanceof groovy.lang.Script;
assert script.run().equals(15);
}
}
可以使用groovy.lang.Binding
在应用程序和脚本之间共享数据:
// 创建一个包含共享数据的新Binding
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
def now = new Date()
// 将字符串添加到绑定中
sharedData.setProperty('text', 'I am shared data!')
// 向绑定添加日期(不限于简单类型)
sharedData.setProperty('date', now)
String result = shell.evaluate('"At $date, $text"')
assert result == "At $now, I am shared data!"
同样,可以读取脚本绑定的值:
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate('foo=123')
// 读取调用者的结果
assert sharedData.getProperty('foo') == 123
在多线程环境中使用共享数据时必须非常小心。您传递给GroovyShell
的Binding
实例不是线程安全的,并且由所有脚本共享。
Script
我们已经看到parse
方法返回groovy.lang.Script
的实例,但可以使用自定义类,因为它扩展了Script
本身。它可用于为脚本提供附加行为,如下例所示:
abstract class MyScript extends Script {
String name
String greet() {
"Hello, $name!"
}
}
GroovyClassLoader
我们已经展示了GroovyShell
是一个执行脚本的简单工具,但它使得编译除脚本之外的任何内容都变得复杂。在内部,它使用groovy.lang.GroovyClassLoader
,它是运行时编译和加载类的核心。
通过利用GroovyClassLoader
而不是GroovyShell
,您将能够加载类,而不是脚本实例:
import groovy.lang.GroovyClassLoader
def gcl = new GroovyClassLoader()
def clazz = gcl.parseClass('class Foo { void doIt() { println "ok" } }')
assert clazz.name == 'Foo'
def o = clazz.newInstance()
o.doIt()
GroovyClassLoader 保留它创建的所有类的引用,因此很容易造成内存泄漏。特别是,如果您执行相同的脚本两次,如果它是一个字符串,那么您将获得两个不同的类!
import groovy.lang.GroovyClassLoader
def gcl = new GroovyClassLoader()
// 使用单独的parseClass调用创建一个外观相同的类
def clazz1 = gcl.parseClass('class Foo { }')
def clazz2 = gcl.parseClass('class Foo { }')
assert clazz1.name == 'Foo'
assert clazz2.name == 'Foo'
// 但它们实际上是不同的!
assert clazz1 != clazz2
原因是GroovyClassLoader
不跟踪源文本。如果您想拥有相同的实例,那么源必须是一个文件,如下例所示:
def gcl = new GroovyClassLoader()
def clazz1 = gcl.parseClass(file)
// 从不同的文件实例解析类,但指向相同的物理文件
def clazz2 = gcl.parseClass(new File(file.absolutePath))
assert clazz1.name == 'Foo'
assert clazz2.name == 'Foo'
assert clazz1 == clazz2
GroovyScriptEngine
groovy.util.GroovyScriptEngine
类为依赖脚本重新加载和脚本依赖项的应用程序提供了灵活的基础。虽然GroovyShell
专注于独立Script
s and GroovyClassLoader
处理任何 Groovy 类的动态编译和加载,但GroovyScriptEngine
将在GroovyClassLoader
之上添加一个层来处理脚本依赖项和重新加载。
class Greeter {
String sayHello() {
def greet = "Hello, world!"
greet
}
}
new Greeter()
def binding = new Binding()
def engine = new GroovyScriptEngine([tmpDir.toURI().toURL()] as URL[])
while (true) {
def greeter = engine.run('ReloadingTest.groovy', binding)
println greeter.sayHello()
Thread.sleep(1000)
}
JSR-223脚本:javax.script API
JSR-223 是用于调用 Java 脚本框架的标准 API。它从 Java 6 开始可用,旨在提供一个从 Java 调用多种语言的通用框架。 Groovy 提供了自己更丰富的集成机制,如果您不打算在同一应用程序中使用多种语言,建议您使用 Groovy 集成机制而不是有限的 JSR-223 API。
以下是初始化 JSR-223 引擎以从 Java 与 Groovy 对话的方法:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
...
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("groovy");
然后您可以轻松执行 Groovy 脚本:
Integer sum = (Integer) engine.eval("(1..10).sum()");
assertEquals(Integer.valueOf(55), sum);
也可以共享变量:
engine.put("first", "HELLO");
engine.put("second", "world");
String result = (String) engine.eval("first.toLowerCase() + ' ' + second.toUpperCase()");
assertEquals("hello WORLD", result);
下一个示例说明了调用可调用函数:
import javax.script.Invocable;
...
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("groovy");
String fact = "def factorial(n) { n == 1 ? 1 : n * factorial(n - 1) }";
engine.eval(fact);
Invocable inv = (Invocable) engine;
Object[] params = {5};
Object result = inv.invokeFunction("factorial", params);
assertEquals(Integer.valueOf(120), result);
引擎默认保留对脚本函数的硬引用。要更改此设置,您应该将引擎级作用域属性设置为名称#jsr223.groovy.engine.keep.globals
的脚本上下文,其中字符串为phantom
以使用幻像引用, weak
为使用弱引用或soft
为使用软引用 -大小写被忽略。任何其他字符串都会导致使用硬引用。
进阶应用
案例:动态编程
定义业务接口,通过Groovy脚本实现动态编程。可以将脚本存放到指定路径或者数据库中
- 定义业务接口
package cn.yujian95.learn.groovy.java;
import java.util.List;
/**
* @author yujian yujian95_cn@163.com
*/
public interface IBizFilter {
// 过滤逻辑
List<String> filter(List<String> list);
}
- 编写Groovy脚本,实现接口编写业务逻辑
package cn.yujian95.learn.groovy.java
import java.util.stream.Collectors
// 必须显示导入接口
import cn.yujian95.learn.groovy.java.IBizFilter
class BizFilterStrategyA implements IBizFilter {
@Override
List<String> filter(List<String> list) {
return list.stream().filter({ o -> ("A" == o) }).collect(Collectors.toList())
}
}
- 编写Groovy工具类,编译脚本实例
package cn.yujian95.learn.groovy.java;
import cn.hutool.core.lang.Assert;
import groovy.lang.GroovyClassLoader;
import lombok.extern.slf4j.Slf4j;
/**
* @author yujian yujian95_cn@163.com
*/
@Slf4j
public class GroovyUtil {
private static final GroovyClassLoader classLoader = new GroovyClassLoader(BizFilterDemo.class.getClassLoader());
public static IBizFilter compile(String classScript) {
Assert.notEmpty(classScript, "脚本不能为空!");
Object newInstance = null;
try {
Class groovyClass = classLoader.parseClass(classScript);
newInstance = groovyClass.newInstance();
if (!(newInstance instanceof IBizFilter)) {
// 错误处理
log.error("未实现接口");
return null;
}
} catch (Exception exception) {
// 异常处理
log.error("异常信息:{}" , exception.getMessage());
}
if (newInstance == null) {
return null;
}
// 将实例转换为接口
return IBizFilter.class.cast(newInstance);
}
}
- 读取Groovy脚本,并执行业务逻辑
package cn.yujian95.learn.groovy.java;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
/**
* @author yujian yujian95_cn@163.com
*/
@Slf4j
public class BizFilterDemo {
public static List<String> initTestData() {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
return list;
}
public static void main(String[] args) {
// 读取脚本内容
IBizFilter iBizFilter = GroovyUtil.compile("import java.util.stream.Collectors\n" +
"import cn.yujian95.learn.groovy.java.IBizFilter\n" +
"\n" +
"class BizFilterStrategyA implements IBizFilter {\n" +
"\n" +
" BizFilterStrategyA() {}\n" +
"\n" +
" @Override\n" +
" List<String> filter(List<String> list) {\n" +
" return list.stream().filter({ o -> (\"A\" == o) }).collect(Collectors.toList())\n" +
" }\n" +
"}");
if (iBizFilter != null) {
System.out.println(iBizFilter.filter(initTestData()));
}
}
}
扩展:安全性优化(WIP)
总结
通过学习Groovy,我们可以为自己的开发技能增加新的元素和工具,提高我们的编程效率和代码质量,希望这篇文章能够帮助大家更好地了解和应用Groovy
这个强大的编程语言。
相关学习资料:
评论