美文网首页
Integrating Groovy into applicat

Integrating Groovy into applicat

作者: 坚持到底v2 | 来源:发表于2018-03-27 14:38 被阅读0次

    文档

    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 applicat

          本文链接:https://www.haomeiwen.com/subject/ulfscftx.html