文档
http://www.groovy-lang.org/integrating.html
1、Groovy integration mechanisms
Groovy语言提出了几种在运行时将自身集成到应用程序(Java甚至Groovy)中的方法,
从最基本的简单代码执行到最完整的 integrating caching and compiler customization 。
1.1 Eval
最简单的方式
import groovy.util.Eval
assert Eval.me('33*3') == 99
assert Eval.me('"foo".toUpperCase()') == 'FOO'
assert Eval.x(4, '2*x') == 8
assert Eval.me('k', 4, '2*k') == 8
assert Eval.xy(4, 5, 'x*y') == 20
assert Eval.xyz(4, 5, 6, 'x*y+z') == 26
1.2. GroovyShell
1.2.1 Multiple sources
groovy.lang.GroovyShell
类 是 evaluate 脚本,并且带有缓存结果的首选方法。
尽管 Eval
类可以返回 编译的脚本 执行的结果,但GroovyShell
类提供了更多的选项。
def shell = new GroovyShell()
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
1.2.2 Sharing data between a script and the application
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!"
注意 Binding
是双向的,即可以从脚本中想 Binding
写数据
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate('foo=123')
assert sharedData.getProperty('foo') == 123
注意如果你想向sharedData中写数据,你需要使用 未声明的变量。
使用 def 或 显式类型声明的变量将作为 local variable
而不会写入到 sharedData 中
在多线程环境中,你必须小心。Binding
不是线程安全的,被所有 scripts 共享
Binding
对象可以动态绑定到 parse
返回的 Script
实例
def shell = new GroovyShell()
def b1 = new Binding(x:3)
def b2 = new Binding(x:4)
def script = shell.parse('x = 2*x')
script.binding = b1
script.run()
script.binding = b2
script.run()
assert b1.getProperty('x') == 6
assert b2.getProperty('x') == 8
assert b1 != b2
1.2.3. Custom script class
我们已经看到了 parse
方法会返回一个 groovy.lang.Script
对象,
你可以使用自定义的类,继承 Script
即可。
abstract class MyScript extends Script {
String name
String greet() {
"Hello, $name!"
}
}
import org.codehaus.groovy.control.CompilerConfiguration
def config = new CompilerConfiguration()
config.scriptBaseClass = 'MyScript'
def shell = new GroovyShell(this.class.classLoader, new Binding(), config)
def script = shell.parse('greet()')
assert script instanceof MyScript
script.setName('Michel')
assert script.run() == 'Hello, Michel!'
1.3. GroovyClassLoader
虽然 GroovyShell
很容易使用,但是它仅限于执行 scripts
如果要编译除了 scripts 之外的东西它就不够用了
其实在内部, 它使用了 groovy.lang.GroovyClassLoader
,它是运行时 编译和加载类 的核心。
通过使用 GroovyClassLoader
,你可以加载类,而不是 scripts 实例。
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
会持有它创建的类的引用,所以很容易造成 memory leak。
尤其是 如果你对相同的 script 执行了两次,那么你也会得到两个不同的classes
import groovy.lang.GroovyClassLoader
def gcl = new GroovyClassLoader()
def clazz1 = gcl.parseClass('class Foo { }')
def clazz2 = gcl.parseClass('class Foo { }')
assert clazz1.name == 'Foo'
assert clazz2.name == 'Foo'
assert clazz1 != clazz2
这是因为 GroovyClassLoader
不会追踪 source text。
如果你想得到两个相同的实例,你必须将source放到一个文件中,如下所示:
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
1.4. GroovyScriptEngine
groovy.util.GroovyScriptEngine
为依赖于 script reloading 和 script dependencies 的应用程序提供了灵活的Foundation。
GroovyShell
专注于独立脚本,
GroovyClassLoader
可以处理任何Groovy类的动态编译和加载,
而 GroovyScriptEngine
会在 GroovyClassLoader
之上添加一个层来处理 script reloading 和 script dependencies
例子:
首先创建一个脚本文件 ReloadingTest.groovy
class Greeter {
String sayHello() {
def greet = "Hello, world!"
greet
}
}
new Greeter()
然后使用 GroovyScriptEngine
执行:
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)
}
此时每隔1秒你都会看到一个message被打印
不要打断脚本的执行,现在更改 ReloadingTest.groovy
的内容
class Greeter {
String sayHello() {
def greet = "Hello, Groovy!"
greet
}
}
new Greeter()
你会看到message变了
当然,你也依赖于其他脚本。
例如创建一个 Depencency.groovy
class Dependency {
String message = 'Hello, dependency 1'
}
然后更改 ReloadingTest.groovy
的内容
import Dependency
class Greeter {
String sayHello() {
def greet = new Dependency().message
greet
}
}
new Greeter()
然后再更改 Depencency.groovy
的内容,同样会改变 message 内容
2、JSR 223 javax.script API
JSR 223 是Java中的calling scripting frameworks 的标准API。
自 Java 6开始引入,并且致力于提供一个通用的framework,以便在Java中调用各种类型的语言。
Groovy提供了更丰富的集成机制,如果您不打算在同一个应用程序中使用多种语言,建议您使用Groovy集成机制,而不是有限的JSR-223 API。
初始化 JSR-223 engine
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(new Integer(55), sum);
也可以共享变量
engine.put("first", "HELLO");
engine.put("second", "world");
String result = (String) engine.eval("first.toLowerCase() + ' ' + second.toUpperCase()");
assertEquals("hello WORLD", result);
调用 invokable function
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(new Integer(120), result);
engine 默认 保持对脚本函数的硬引用(hard references)。
要改变这个,你应该在 引擎级的作用域 上设置 script context 的 #jsr223.groovy.engine.keep.globals
属性为 phantom/weak/soft , 任何其他的字符串都会导致使用 hard 引用