Integrating Groovy into applications

文档

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 引用

你可能感兴趣的:(Integrating Groovy into applications)