导购场景下的Groovy脚本引擎实战

其实这一切都要归功于编译器,编译器在编译代码的时候,并不是像 Java 一样,直接编译成字节码,而是编译成 “动态调用的字节码”
例如下面这一段代码
package groovy println("Hello World!")
当我们用编译器编译之后,就会变成
package groovy;......public class HelloGroovy extends Script {private static /* synthetic */ ClassInfo $staticClassInfo;public static transient /* synthetic */ boolean __$stMC;private static /* synthetic */ ClassInfo $staticClassInfo$;private static /* synthetic */ SoftReference $callSiteArray;......public static void main(String ... args) {// 调用run()方法CallSite[] arrcallSite = HelloGroovy.$getCallSiteArray();arrcallSite[0].call(InvokerHelper.class, HelloGroovy.class, (Object)args);}public Object run() {CallSite[] arrcallSite = HelloGroovy.$getCallSiteArray();return arrcallSite[1].callCurrent((GroovyObject)this, (Object)"Hello World!");}......private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {arrstring[0] = "runScript";arrstring[1] = "println";}......}
动态方法执行原理
在中动态地注入方法、调用方法、属性就是使用元类来完成的(类似于Java的反射机制),请求的方法会被委托到这个类 。
与java编译成字节码时处理方法调用不同,编译时对方法调用通用都是通过实现,这样提供了极强的动态方法植入能力.
所有 脚本生成的class 都会实现接口, 里面所有方法调用都会通过来调用.
and
存储了所有 包含java 的,的 的 实际是由来执行的
4.的使用方式
在java项目中的使用方式基本上分为三种:、和 。
4.2.1
从指定的位置(文件系统,URL,数据库,等等)加载脚本并执行
//定义FunGroove.groovy文件 package com.chy.groovyvoid print(){System.out.println("没有参数!!!!");}//执行方法print();// GroovyScriptEngine的根路径,如果参数是字符串数组,说明有多个根路径GroovyScriptEngine engine = new GroovyScriptEngine("src/main/java/com/chy/groovy/");Binding binding1 = new Binding();Object result1 = engine.run("FunGroove.groovy", binding1);if(null!=result1) {System.out.println(result1);}
4.2.2
官方提供,执行脚本片段,每一次执行时代码时会动态将代码编译成Java Class,然后生成Java对象在Java虚拟机上执行,所以如果使用会造成Class太多,性能较差 。

导购场景下的Groovy脚本引擎实战

文章插图
final String script = "Runtime.getRuntime().availableProcessors()";Binding intBinding = new Binding();GroovyShell shell = new GroovyShell(intBinding);final Object eval = shell.evaluate(script);System.out.println(eval);
4.2.3
官方提供类,支持从文件、url或字符串中动态地加载一个脚本并执行它的行为,实例化对象,反射调用指定方法 。是一个定制的类装载器,负责解释加载Java类中用到的类 。
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();String helloScript = "package com.vivo.groovy.util" +// groovy或者Java代码"class Hello {" +"String say(String name) {" +"System.out.println(\"hello, \" + name)" +" return name;""}" +"}";Class helloClass = groovyClassLoader.parseClass(helloScript);GroovyObject object = (GroovyObject) helloClass.newInstance();Object ret = object.invokeMethod("say", "world"); // 输出"hello, world"System.out.println(ret.toString()); // 打印
4.使用优化
当JVM中运行的脚本存在大量并发时,如果按照默认的策略,每次运行都会重新编译脚本,调用类加载器进行类加载 。不断重新编译脚本会增加JVM内存中的和,引发内存泄露,最后导致内存溢出问题