Groovy集成机制
Groovy语言提出了几种在运行时将其自身集成到应用程序(Java甚至Groovy)中的方法,从最基本,最简单的代码执行到最完整的集成缓存和编译器定制。
本节中编写的所有示例都使用Groovy,但是可以从Java使用相同的集成机制。
1.1 Eval
groovy.util.Eval是在运行时动态执行Groovy的最简单方法。可以通过调用以下me 方法来完成:
import groovy.util.Eval
assert Eval.me('33*3') == 99
assert Eval.me('"foo".toUpperCase()') == 'FOO'
1.2 GroovyShell
1.2.1 多种来源
groovy.lang.GroovyShell是缓存生成的脚本实例的能力来运行脚本的首选方式。尽管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
创建一个新GroovyShell实例
可以Eval与直接执行代码一起使用
可以从多个源中读取(String,Reader,File,InputStream)
可以推迟脚本的执行。parse返回一个Script实例
Script定义run方法
1.2.2 在脚本和应用程序之间共享数据
使用groovy.lang.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!"
创建一个Binding将包含共享数据的新文件
GroovyShell使用此共享数据创建一个
在绑定中添加一个字符串
在绑定中添加日期(您不仅限于简单类型)
运行脚本
请注意,也可以从脚本写入绑定:
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate('foo=123')
assert sharedData.getProperty('foo') == 123
创建一个新Binding实例
GroovyShell使用该共享数据创建一个新的
使用未声明的变量将结果存储到绑定中
读取呼叫者的结果
重要的是,如果要写入绑定,则需要使用未声明的变量。在下面的示例中使用 def将失败,因为您将创建一个局部变量:
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate('int foo=123')
try {
assert sharedData.getProperty('foo')
} catch (MissingPropertyException e) {
println "foo is defined as a local variable"
}
1.2.3 自定义脚本类
我们已经看到,parse方法返回的实例groovy.lang.Script,但是可以使用自定义类,因为它可以扩展Script自身。它可以用来为脚本提供其他行为,如以下示例所示:
abstract class MyScript extends Script {
String name
String greet() {
"Hello, $name!"
}
}
自定义类定义了一个名为的属性name和一个名为的新方法greet。通过使用自定义配置,可以将该类用作脚本基类:
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!'
创建一个CompilerConfiguration实例
指示它MyScript用作脚本的基类
然后在创建外壳时使用编译器配置
脚本现在可以访问新方法 greet
您不仅限于单一的scriptBaseClass配置。您可以使用任何编译器配置调整,包括编译定制器。
1.3 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
parseClass 将返回一个实例 Class
您可以检查返回的类是否确实是脚本中定义的类
您可以创建该类的新实例,而不是脚本
然后在上面调用任何方法
GroovyClassLoader保留对其创建的所有类的引用,因此很容易造成内存泄漏。特别是,如果您两次执行相同的脚本(如果它是一个字符串),那么您将获得两个不同的类!
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不能跟踪源文本。如果要具有相同的实例,则源必须是文件,如本示例所示:
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
解析一个类 File
从不同的文件实例解析一个类,但指向同一物理文件
确保我们的课程名称相同
但是现在,它们是同一个实例
使用File作为输入,GroovyClassLoader可以缓存生成的类文件,从而避免在运行时为同一源创建多个类。