Groovy本身是一门完整的编程语言,同时也可以作为脚本集成到我们的应用中,为静态语言提供一些动态的能力,比如将 groovy 的脚本放到一些动态配置中或者数据库中进行管理,实现一些动态的规则计算等,在规则新增或变更的时候可以实时更新而无需开发代码上线,这次主要介绍一下Java中如何使用Groovy脚本
关于在应用中集成Groovy,官方文档已经提供了相关的资料,本次基于此文档进行一下说明
首先需要引入相应的包,这里基于的版本是3.0.16
1 2 3 4 5
| <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>3.0.16</version> </dependency>
|
Eval
Eval是最简单的一种用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import groovy.util.Eval;
Eval.me("33*3"); Eval.me("'foo'.toUpperCase()");
Eval.x(4, "2 * x");
Eval.me("k", 4, "2 * k");
Eval.xy(4, 5, "x * y");
Eval.xyz(4, 5, 6, "x * y + z");
|
表达式中可以调用自定义类的方法,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Data @AllArgsConstructor static class Goods { private String goodsName; private BigDecimal price; private Integer num;
public BigDecimal totalPrice() { return price.multiply(new BigDecimal(num)); } }
Goods goods = new Goods("goods1", new BigDecimal("100.54"), 5); Object result = Eval.me("goods", goods, "goods.totalPrice()");
|
同时,表达式中也支持多行
1 2 3 4 5 6
| Object result = Eval.me("def i = 5; def j = 6; i + j");
Object result = Eval.me("def run1() {def i = 5;def j = 's'; j + i}; run1()");
|
但是使用Eval需要注意,它是不支持缓存的,每次执行都会创建类(会占用永久代/元空间),虽然占用的空间在GC的时候会被回收,不过表达式需要频繁执行的话,还是尽量不要使用
GroovyShell
GroovyShell可以认为是Eval的升级版,主要有如下几个方面的改进
- 支持多种脚本来源,而不仅仅只能通过字符串传递脚本
1 2
| URI uri = new URL("https://example.test/script.groovy").toURI(); Object evaluate = groovyShell.evaluate(uri);
|
- 支持设置和返回更多的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Binding sharedData = new Binding(); GroovyShell shell = new GroovyShell(sharedData); sharedData.setProperty("text", "I am shared data"); sharedData.setProperty("num", 10); Object result = shell.evaluate("text + ': ' + num");
Binding sharedData = new Binding(); GroovyShell shell = new GroovyShell(sharedData);
Object result = shell.evaluate("i = 10; i + 1");
Object i = sharedData.getProperty("i");
|
- 可以先解析脚本,延迟执行
1 2 3 4 5 6 7 8 9 10 11 12
| GroovyShell shell = new GroovyShell(); Script script = shell.parse("4 * num");
Binding binding = new Binding(); binding.setProperty("num", 7); script.setBinding(binding);
Object run = script.run();
|
GroovyShell每次执行时也是会创建新的类,解析创建的Script实例是可以进行缓存的,避免每次都加载新的类和创建对应实例,但是因为涉及到外部绑定的变量,所以如果变量有变更,在并发的情况下会有问题,不能在并发情况下使用
GroovyClassLoader
之前说过的不管是 Eval 还是 GroovyShell ,它们最终都是使用 GroovyClassLoader进行脚本的解析、创建并加载类来执行的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| GroovyClassLoader classLoader = new GroovyClassLoader(); Class aClass = classLoader.parseClass("class Foo {String doIt() {return \"ok\"}}"); Object object = aClass.newInstance(); Method method = object.getClass().getDeclaredMethod("doIt"); Object invoke = method.invoke(object);
GroovyClassLoader classLoader = new GroovyClassLoader(); Class aClass = classLoader.parseClass("def doIt(i) {return \"ok \" + i}"); Object object = aClass.newInstance(); Method method = object.getClass().getDeclaredMethod("doIt", Object.class); Object invoke = method.invoke(object, 6);
GroovyClassLoader classLoader = new GroovyClassLoader(); Class aClass = classLoader.parseClass("3 + 5"); Script object = (Script) aClass.newInstance(); Object result = object.run();
|
使用这种方式的时候,我们可以缓存创建的实例和对应的方法,之后就可以根据这两个信息进行方法的调用了,同时如果脚本类写的没有问题时,也不会有并发的问题
以上就是在 Java 项目中使用 Groovy脚本的几种主要方式,大家可以根据具体的使用场景进行选择,尽量缓存创建的实例