最近设计一个数据统计系统,系统中上百种数据统计维度,而且这些数据统计的指标可能随时会调整.如果基于java编码的方式逐个实现数据统计的API设计,工作量大而且维护起来成本较高;最终确定为将"数据统计"的计算部分单独分离成脚本文件(javascript,或者Groovy),非常便捷了实现了"数据统计Task" 与 "数据统计规则(计算)"解耦,且可以动态的加载和运行的能力.顺便对JAVA嵌入运行Groovy脚本做个备忘.
Java中运行Groovy,有三种比较常用的类支持:GroovyShell,GroovyClassLoader以及Java-Script引擎(JSR-223).
1) GroovyShell: 通常用来运行"script片段"或者一些零散的表达式(Expression)
2) GroovyClassLoader: 如果脚本是一个完整的文件,特别是有API类型的时候,比如有类似于JAVA的接口,面向对象设计时,通常使用GroovyClassLoader.
3) ScriptEngine: JSR-223应该是推荐的一种使用策略.规范化,而且简便.
一.GroovyShell代码样例
1) 简单的表达式执行,方法调用
- /**
- *简答脚本执行
- *@throwsException
- */
- publicstaticvoidevalScriptText()throwsException{
- //groovy.lang.Binding
- Bindingbinding=newBinding();
- GroovyShellshell=newGroovyShell(binding);
- binding.setVariable("name","zhangsan");
- shell.evaluate("println'HelloWorld!Iam'+name;");
- //在script中,声明变量,不能使用def,否则scrope不一致.
- shell.evaluate("date=newDate();");
- Datedate=(Date)binding.getVariable("date");
- System.out.println("Date:"+date.getTime());
- //以返回值的方式,获取script内部变量值,或者执行结果
- //一个shell实例中,所有变量值,将会在此"session"中传递下去."date"可以在此后的script中获取
- Longtime=(Long)shell.evaluate("deftime=date.getTime();returntime;");
- System.out.println("Time:"+time);
- binding.setVariable("list",newString[]{"A","B","C"});
- //invokemethod
- StringjoinString=(String)shell.evaluate("defcall(){returnlist.join('-')};call();");
- System.out.println("Arrayjoin:"+joinString);
- shell=null;
- binding=null;
- }
2) 伪main方法执行.
- /**
- *当groovy脚本,为完整类结构时,可以通过执行main方法并传递参数的方式,启动脚本.
- */
- publicstaticvoidevalScriptAsMainMethod(){
- String[]args=newString[]{"Zhangsan","10"};//main(String[]args)
- Bindingbinding=newBinding(args);
- GroovyShellshell=newGroovyShell(binding);
- shell.evaluate("staticvoidmain(String[]args){if(args.length!=2)return;println('Hello,Iam'+args[0]+',age'+args[1])}");
- shell=null;
- binding=null;
- }
3) 通过Shell运行具有类结构的Groovy脚本
- /**
- *运行完整脚本
- *@throwsException
- */
- publicstaticvoidevalScriptTextFull()throwsException{
- StringBufferbuffer=newStringBuffer();
- //defineAPI
- buffer.append("classUser{")
- .append("Stringname;Integerage;")
- //.append("User(Stringname,Integerage){this.name=name;this.age=age};")
- .append("StringsayHello(){return'Hello,Iam'+name+',age'+age;}}\n");
- //Usage
- buffer.append("defuser=newUser(name:'zhangsan',age:1);")
- .append("user.sayHello();");
- //groovy.lang.Binding
- Bindingbinding=newBinding();
- GroovyShellshell=newGroovyShell(binding);
- Stringmessage=(String)shell.evaluate(buffer.toString());
- System.out.println(message);
- //重写main方法,默认执行
- StringmainMethod="staticvoidmain(String[]args){defuser=newUser(name:'lisi',age:12);print(user.sayHello());}";
- shell.evaluate(mainMethod);
- shell=null;
- }
4) 方法执行和分部调用
- /**
- *以面向"过程"的方式运行脚本
- *@throwsException
- */
- publicstaticvoidevalScript()throwsException{
- Bindingbinding=newBinding();
- GroovyShellshell=newGroovyShell(binding);
- //直接方法调用
- //shell.parse(newFile(//))
- Scriptscript=shell.parse("defjoin(String[]list){returnlist.join('--');}");
- StringjoinString=(String)script.invokeMethod("join",newString[]{"A1","B2","C3"});
- System.out.println(joinString);
- 脚本可以为任何格式,可以为main方法,也可以为普通方法
- //1)defcall(){...};call();
- //2)call(){...};
- script=shell.parse("staticvoidmain(String[]args){i=i*2;}");
- script.setProperty("i",newInteger(10));
- script.run();//运行,
- System.out.println(script.getProperty("i"));
- //thesameas
- System.out.println(script.getBinding().getVariable("i"));
- script=null;
- shell=null;
- }
二. GroovyClassLoader代码示例
1) 解析groovy文件
- /**
- *fromsourcefileof*.groovy
- */
- publicstaticvoidparse()throwsException{
- GroovyClassLoaderclassLoader=newGroovyClassLoader(Thread.currentThread().getContextClassLoader());
- FilesourceFile=newFile("D:\\TestGroovy.groovy");
- ClasstestGroovyClass=classLoader.parseClass(newGroovyCodeSource(sourceFile));
- GroovyObjectinstance=(GroovyObject)testGroovyClass.newInstance();//proxy
- Longtime=(Long)instance.invokeMethod("getTime",newDate());
- System.out.println(time);
- Datedate=(Date)instance.invokeMethod("getDate",time);
- System.out.println(date.getTime());
- //here
- instance=null;
- testGroovyClass=null;
- }
2) 如何加载已经编译的groovy文件(.class)
- publicstaticvoidload()throwsException{
- GroovyClassLoaderclassLoader=newGroovyClassLoader(Thread.currentThread().getContextClassLoader());
- BufferedInputStreambis=newBufferedInputStream(newFileInputStream("D:\\TestGroovy.class"));
- ByteArrayOutputStreambos=newByteArrayOutputStream();
- for(;;){
- inti=bis.read();
- if(i==-1){
- break;
- }
- bos.write(i);
- }
- ClasstestGroovyClass=classLoader.defineClass(null,bos.toByteArray());
- //instanceofproxy-class
- //ifinterfaceAPIisintheclasspath,youcandosuchas:
- //MyObjectinstance=(MyObject)testGroovyClass.newInstance()
- GroovyObjectinstance=(GroovyObject)testGroovyClass.newInstance();
- Longtime=(Long)instance.invokeMethod("getTime",newDate());
- System.out.println(time);
- Datedate=(Date)instance.invokeMethod("getDate",time);
- System.out.println(date.getTime());
- //here
- bis.close();
- bos.close();
- instance=null;
- testGroovyClass=null;
- }
三. ScriptEngine
1) pom.xml依赖
- <dependency>
- <groupId>org.codehaus.groovy</groupId>
- <artifactId>groovy</artifactId>
- <version>2.1.6</version>
- </dependency>
- <dependency>
- <groupId>org.codehaus.groovy</groupId>
- <artifactId>groovy-jsr223</artifactId>
- <version>2.1.6</version>
- </dependency>
2) 代码样例
- publicstaticvoidevalScript()throwsException{
- ScriptEngineManagerfactory=newScriptEngineManager();
- //每次生成一个engine实例
- ScriptEngineengine=factory.getEngineByName("groovy");
- System.out.println(engine.toString());
- assertengine!=null;
- //javax.script.Bindings
- Bindingsbinding=engine.createBindings();
- binding.put("date",newDate());
- //如果script文本来自文件,请首先获取文件内容
- engine.eval("defgetTime(){returndate.getTime();}",binding);
- engine.eval("defsayHello(name,age){return'Hello,Iam'+name+',age'+age;}");
- Longtime=(Long)((Invocable)engine).invokeFunction("getTime",null);
- System.out.println(time);
- Stringmessage=(String)((Invocable)engine).invokeFunction("sayHello","zhangsan",newInteger(12));
- System.out.println(message);
- }
需要提醒的是,在groovy中,${expression} 将会被认为一个变量,如果需要输出"$"符号,需要转义为"\$".
关于ScriptEngine更多介绍,请参考.