Groovy 与应用的集成
1. Groovy 集成机制Groovy 语言提供了几种在运行时与应用(由 Java 或 Groovy 所编写)相集成的机制,涉及到了从最基本的简单代码执行,到最完整的集成缓存和编译器自定义设置等诸多方面。 本部分内容所有范例都是用 Groovy 编写的,但这样的机制也可以用于 Java 编写的应用程序。 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 1?? assert Eval.me('k',4,'2*k') == 8 2?? assert Eval.xy(4,5,'x*y') == 20 3?? assert Eval.xyz(4,6,'x*y+z') == 26 4?? 1?? 带有一个名为
1.2 GroovyShell1.2.1 多数据源
def shell = new GroovyShell() 1?? def result = shell.evaluate '3*5' 2?? def result2 = shell.evaluate(new StringReader('3*5')) 3?? assert result == result2 def script = shell.parse '3*5' 4?? assert script instanceof groovy.lang.Script assert script.run() == 15 5?? 1?? 创建一个新的 1.2.2 在脚本与程序间共享数据使用 def sharedData = new Binding() 1?? def shell = new GroovyShell(sharedData) 2?? def now = new Date() sharedData.setProperty('text','I am shared data!') 3?? sharedData.setProperty('date',now) 4?? String result = shell.evaluate('"At $date,$text"') 5?? assert result == "At $now,I am shared data!" 1?? 创建一个包含共享数据的 注意,也可以从脚本写入绑定对象。 def sharedData = new Binding() 1?? def shell = new GroovyShell(sharedData) 2?? shell.evaluate('foo=123') 3?? assert sharedData.getProperty('foo') == 123 4?? 1?? 创建一个新的 这里重要的一点是,如果想写入绑定对象,必须要使用未声明变量。使用 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" } 在多线程环境中使用共享数据应该极为小心。传入 利用被 def shell = new GroovyShell() def b1 = new Binding(x:3) 1?? def b2 = new Binding(x:4) 2?? 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?? 在 但是,必须注意,此时仍旧共享的是脚本的同一个实例。因此,如果两个线程都要利用同一脚本,就不能采用这种方法,这时必须创建两个独立的脚本实例。 def shell = new GroovyShell() def b1 = new Binding(x:3) def b2 = new Binding(x:4) def script1 = shell.parse('x = 2*x') 1?? def script2 = shell.parse('x = 2*x') 2?? assert script1 != script2 script1.binding = b1 3?? script2.binding = b2 4?? def t1 = Thread.start { script1.run() } 5?? def t2 = Thread.start { script2.run() } 6?? [t1,t2]*.join() 7?? assert b1.getProperty('x') == 6 assert b2.getProperty('x') == 8 assert b1 != b2 1?? 为线程 1 创建一个脚本实例。 在需要线程安全的场合(比如该例),建议最好直接使用 GroovyClassLoader。 1.2.3 自定义脚本类如你所见, abstract class MyScript extends Script { String name String greet() { "Hello,$name!" } } 自定义类定义了一个叫 import org.codehaus.groovy.control.CompilerConfiguration def config = new CompilerConfiguration() 1?? config.scriptBaseClass = 'MyScript' 2?? def shell = new GroovyShell(this.class.classLoader,new Binding(),config) 3?? def script = shell.parse('greet()') 4?? assert script instanceof MyScript script.setName('Michel') assert script.run() == 'Hello,Michel!' 1?? 创建一个 并不局限于只使用 scriptBaseClass 配置。可以使用任何编译器配置微调选项,包括 compilation customizers。 1.3 GroovyClassLoader上一部分内容介绍了 通过利用 import groovy.lang.GroovyClassLoader def gcl = new GroovyClassLoader() 1?? def clazz = gcl.parseClass('class Foo { void doIt() { println "ok" } }') 2?? assert clazz.name == 'Foo' 3?? def o = clazz.newInstance() 4?? o.doIt() 5?? 1?? 创建一个新的
import groovy.lang.GroovyClassLoader def gcl = new GroovyClassLoader() def clazz1 = gcl.parseClass('class Foo { }') 1?? def clazz2 = gcl.parseClass('class Foo { }') 2?? assert clazz1.name == 'Foo' 3?? assert clazz2.name == 'Foo' assert clazz1 != clazz2 4?? 1?? 动态创建一个名为 原因在于, def gcl = new GroovyClassLoader() def clazz1 = gcl.parseClass(file) 1?? def clazz2 = gcl.parseClass(new File(file.absolutePath)) 2?? assert clazz1.name == 'Foo' 3?? assert clazz2.name == 'Foo' assert clazz1 == clazz2 4?? 1?? 从 将 1.4 GroovyScriptEngine
为了说明这一点,下面来创建脚本引擎,用无限循环来执行脚本。首先需要创建一个目录,将下列脚本(ReloadingTest.groovy)放入其中。 ReloadingTest.groovy class Greeter { String sayHello() { def greet = "Hello,world!" greet } } new Greeter() 然后使用 def binding = new Binding() def engine = new GroovyScriptEngine([tmpDir.toURI().toURL()] as URL[]) 1?? while (true) { def greeter = engine.run('ReloadingTest.groovy',binding) 2?? println greeter.sayHello() 3?? Thread.sleep(1000) } 1?? 创建一个脚本引擎,在源目录中寻找数据源。 然后,你就会发现每秒都会输出问候信息,如下所示: Hello,world! Hello,world! ... 不用打断脚本执行过程,现在用下面的内容来替代 ReloadingTest.groovy class Greeter { String sayHello() { def greet = "Hello,Groovy!" greet } } new Greeter() 于是,输出信息就变为: Hello,world! ... Hello,Groovy! Hello,Groovy! ... 但它还可能会依赖其他脚本。接下来在同一目录中创建下面这个文件,同样不用干扰上述脚本执行: 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() 这时,输出消息应变为: Hello,Groovy! ... Hello,dependency 1! Hello,dependency 1! ... 作为最后一项测试,下面我们在不改动 Depencency.groovy class Dependency { String message = 'Hello,dependency 2' } 可以看到重新加载了依赖文件: Hello,dependency 1! ... Hello,dependency 2! Hello,dependency 2! 1.5 CompilationUnit最后,我们直接依靠 但是,不建议重写 2. Bean 脚本框架Bean 脚本框架(BSF,Bean Scripting Framework) 试图通过一个 API 来调用 Java 中的脚本语言。它已经很长时间没有更新过了,但由于支持标准的 JSR-223 API,所以还没有被遗弃。 BSF 引擎由 由于 Groovy 对 Java 集成有着原生的支持,所以你只需要注意下面两种情形中的 BSF 应用:还想调用其他语言时(如 JRuby),或者想与你所使用的脚本语言保持一种非常松散的耦合。 2.1 入门假设在类路径中有 Groovy 和 BSF 的 jar 文件,那么可以使用下列代码运行简单的 Groovy 脚本。 String myScript = "println('Hello World')n return [1,2,3]"; BSFManager manager = new BSFManager(); List answer = (List) manager.eval("groovy","myScript.groovy",myScript); assertEquals(3,answer.size()); 2.2 传入变量BSF 可以使你在 Java 和脚本语言间传入 bean。可以注册/不注册 bean,使其被 BSF 所知晓,然后利用 BSF 方法在需要时查找 bean。另外,还可以声明/不声明 bean。这将注册它们,但也使其能够直接用于脚本语言。第二种方法是 Groovy 通常所习惯采用的方法,如下所示: BSFManager manager = new BSFManager(); manager.declareBean("xyz",Integer.class); Object answer = manager.eval("groovy","test.groovy","xyz + 1"); assertEquals(5,answer); 2.3 其他调用选项上面的范例使用了 BSFManager manager = new BSFManager(); Vector<String> ignoreParamNames = null; Vector<Integer> args = new Vector<Integer>(); args.add(2); args.add(5); args.add(1); Integer actual = (Integer) manager.apply("groovy","applyTest","def summer = { a,b,c -> a * 100 + b * 10 + c }",ignoreParamNames,args); assertEquals(251,actual.intValue()); 2.4 访问脚本引擎虽然不是很常用,但 BSF 还是提供了一种钩子,以便直接访问脚本引擎。引擎所执行的一个函数是在对象上调用一个方法。如下所示: BSFManager manager = new BSFManager(); BSFEngine bsfEngine = manager.loadScriptingEngine("groovy"); manager.declareBean("myvar","hello",String.class); Object myvar = manager.lookupBean("myvar"); String result = (String) bsfEngine.call(myvar,"reverse",new Object[0]); assertEquals("olleh",result); 3 JSR 223 javax.script APIJSR-223 是 Java 中标准的脚本框架调用 API。从 Java 6 开始引入进来,主要目用来提供一种常用框架,以便从 Java 中调用多种语言。由于 Groovy 自身已经提供了更丰富的集成机制,所以如果不想在同一应用中使用多种语言,那么建议使用 Groovy 的集成机制,而不是功能受限的 JSR-223 API。 下面展示的是如何初始化 JSR-223 引擎,从而在 Java 中与 Groovy 建立联系: 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); 下例展示了如何调用一个可调用的方法: 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); 引擎持有脚本函数的每一个默认的硬编码引用。要想改变这一点,可以为名为 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |