动态,敏捷的Groovy

"了解下Groovy的强大吧!"

Posted by Ariescat on January 24, 2019

前言

因为项目中需要用到Groovy语言,所以对它一直有做了解,本文主要总结一下GroovyJavaSpring中的用法。希望本文对准备学习使用或者对 Groovy 感兴趣的同学有所帮助,如有不对之处还望指出哈,对这门语言的理解还是比较肤浅的。

简介

Groovy 是 Apache 旗下的一门基于 JVM 平台的动态/敏捷编程语言,在语言的设计上它吸纳了 Python、Ruby 和 Smalltalk 语言的优秀特性,语法非常简练和优美,开发效率也非常高(编程语言的开发效率和性能是相互矛盾的,越高级的编程语言性能越差,因为意味着更多底层的封装,不过开发效率会更高,需结合使用场景做取舍)。并且,Groovy 可以与 Java 语言无缝对接,在写 Groovy 的时候如果忘记了语法可以直接按Java的语法继续写,也可以在 Java 中调用 Groovy 脚本,都可以很好的工作,这有效的降低了 Java 开发者学习 Groovy 的成本。Groovy 也并不会替代 Java,而是相辅相成、互补的关系,具体使用哪门语言这取决于要解决的问题和使用的场景。

入门

Groovy的简单入门这里就不作讲述,想了解的同学可以参考下面这篇博客:

Spring 支持

从 Spring 2.0 版本开始对动态脚本语言进行了支持(Spring 官方文档该部分地址),其中便包括 Groovy ,Spring 提供了<lang:groovy/>标签来定义 Groovy Bean 。Groovy Bean 可以通过script-source属性加载脚本文件,脚本源文件可以来自本地或者网络,并且可以通过refresh-check-delay属性监听脚本内代码变更重新装载 Bean 实现动态 Bean 。

Calculator.java

public interface Calculator {
	int add(int x, int y);
}

CalculatorGroovyImpl.groovy

    class CalculatorGroovyImpl implements Calculator {
    	int add(int x, int y) {
    		x + y
    	}
    }
#### beans.xml 省略 xmlns
    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
    <lang:groovy id="calculator" script-source="classpath:CalculatorGroovyImpl.groovy" refresh-check-delay="1000"/>
    </beans>

Test.java

public static void main(String[] args) throws InterruptedException {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    Calculator calculator = (Calculator)context.getBean("calculator");
    //尝试修改CalculatorGroovyImpl.groovy,将 x + y,修改为x * y。
    while (true){
        System.out.println(calculator.add(1, 1));
        TimeUnit.SECONDS.sleep(1);
    }
}

与Java集成

Groovy非常容易集成在Java环境中,利用其动态性来做规则引擎、流程引擎、动态脚本环境,非常适合那些不需要经常发布但又经常变更的场景下使用。在Java中集成(调用)Groovy 代码有下面几种方式。

Eval

groovy.util.Eval 类是最简单的用来在运行时动态执行 Groovy 代码的类,提供了几个静态工厂方法供使用,内部其实就是对GroovyShell的封装。

//执行Groovy代码
Eval.me("println 'hello world'");
//绑定自定义参数
Object result = Eval.me("age", 22, "if(age < 18){'未成年'}else{'成年'}");
assertEquals(result, "成年");
//绑定一个名为 x 的参数的简单计算
assertEquals(Eval.x(4, "2*x"), 8);
//带有两个名为 x 与 y 的绑定参数的简单计算
assertEquals(Eval.xy(4, 5, "x*y"), 20);
//带有三个绑定参数(x、y 和 z)的简单计算
assertEquals(Eval.xyz(4, 5, 6, "x*y+z"), 26); 

GroovyShell

groovy.lang.GroovyShell除了可以执行 Groovy 代码外,提供了更丰富的功能,比如可以绑定更多的变量,从文件系统、网络加载代码等。

GroovyShell shell = new GroovyShell();
//可以绑定更多变量
shell.setVariable("age",22);
//直接求值
shell.evaluate("if(age < 18){'未成年'}else{'成年'}");
//解析为脚本,延迟执行或者缓存起来
Script script = shell.parse("if(age < 18){'未成年'}else{'成年'}");
assertEquals(script.run(), "成年");
//可以从更多位置加载/执行脚本
//shell.evaluate(new File("script.groovy"));
//shell.evaluate(new URI("http://wwww.a.com/script.groovy"));

GroovyClassLoader

groovy.lang.GroovyClassLoader是一个定制的类加载器,可以在运行时加载 Groovy 代码,生成 Class 对象。

GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
String scriptText = "class Hello { void hello() { println 'hello' } }";
//将Groovy脚本解析为Class对象
Class clazz = groovyClassLoader.parseClass(scriptText);
//Class clazz = groovyClassLoader.parseClass(new File("script.groovy"));
assertEquals(clazz.getName(),"Hello");
clazz.getMethod("hello").invoke(clazz.newInstance());

GroovyScriptEngine

groovy.util.GroovyScriptEngine能够处理任何 Groovy 代码的动态编译与加载,可以从统一的位置加载脚本,并且能够监听脚本的变化,当脚本发生变化时会重新加载。

/script/groovy/hello.groovy

println "hello $name"

测试类

GroovyScriptEngine scriptEngine = new GroovyScriptEngine("script/groovy");
Binding binding = new Binding();
binding.setVariable("name", "groovy");
while (true){
scriptEngine.run("hello.groovy", binding);
TimeUnit.SECONDS.sleep(1);
}
输出
hello groovy
hello groovy
....
//将hello.groovy内代码修改为println "hi $name", GroovyScriptEngine会重新进行加载
hi groovy
hi groovy

JSR 223 javax.script API

JSR-223 是 Java 中调用脚本语言的标准 API。从 Java 6 开始引入进来,主要目的是用来提供一种统一的框架,以便在 Java 中调用多种脚本语言。JSR-223 支持大部分流行的脚本语言,比如JavaScript、Scala、JRuby、Jython和Groovy等。

ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
Bindings bindings = new SimpleBindings();
bindings.put("age", 22);
Object value = engine.eval("if(age < 18){'未成年'}else{'成年'}",bindings);
assertEquals(value,"成年");

//script/groovy/hello.groovy
//println "hello world"
engine.eval(new FileReader("script/groovy/hello.groovy"));
//hello world

由于 Groovy 自身已经提供了更丰富的集成机制,如果在 Java 应用中只是使用 Groovy 一种脚本语言的话,使用 Groovy 提供的集成机制可能会更合适一点。

后记

感谢以下博客的分享:

—— Ariescat 记于 2019.01.23


喜迎
春节