最近設計一個數據統計系統,系統中上百種數據統計維度,而且這些數據統計的指標可能隨時會調整.如果基於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) 簡單的表達式執行,方法調用
/** * 簡答腳本執行 * @throws Exception */ public static void evalScriptText() throws Exception{ //groovy.lang.Binding Binding binding = new Binding(); GroovyShell shell = new GroovyShell(binding); binding.setVariable("name", "zhangsan"); shell.evaluate("println 'Hello World! I am ' + name;"); //在script中,聲明變量,不能使用def,否則scrope不一致. shell.evaluate("date = new Date();"); Date date = (Date)binding.getVariable("date"); System.out.println("Date:" + date.getTime()); //以返回值的方式,獲取script內部變量值,或者執行結果 //一個shell實例中,所有變量值,將會在此"session"中傳遞下去."date"可以在此后的script中獲取 Long time = (Long)shell.evaluate("def time = date.getTime(); return time;"); System.out.println("Time:" + time); binding.setVariable("list", new String[]{"A","B","C"}); //invoke method String joinString = (String)shell.evaluate("def call(){return list.join(' - ')};call();"); System.out.println("Array join:" + joinString); shell = null; binding = null; }
2) 偽main方法執行.
/** * 當groovy腳本,為完整類結構時,可以通過執行main方法並傳遞參數的方式,啟動腳本. */ public static void evalScriptAsMainMethod(){ String[] args = new String[]{"Zhangsan","10"};//main(String[] args) Binding binding = new Binding(args); GroovyShell shell = new GroovyShell(binding); shell.evaluate("static void main(String[] args){ if(args.length != 2) return;println('Hello,I am ' + args[0] + ',age ' + args[1])}"); shell = null; binding = null; }
3) 通過Shell運行具有類結構的Groovy腳本
/** * 運行完整腳本 * @throws Exception */ public static void evalScriptTextFull() throws Exception{ StringBuffer buffer = new StringBuffer(); //define API buffer.append("class User{") .append("String name;Integer age;") //.append("User(String name,Integer age){this.name = name;this.age = age};") .append("String sayHello(){return 'Hello,I am ' + name + ',age ' + age;}}\n"); //Usage buffer.append("def user = new User(name:'zhangsan',age:1);") .append("user.sayHello();"); //groovy.lang.Binding Binding binding = new Binding(); GroovyShell shell = new GroovyShell(binding); String message = (String)shell.evaluate(buffer.toString()); System.out.println(message); //重寫main方法,默認執行 String mainMethod = "static void main(String[] args){def user = new User(name:'lisi',age:12);print(user.sayHello());}"; shell.evaluate(mainMethod); shell = null; }
4) 方法執行和分部調用
/** * 以面向"過程"的方式運行腳本 * @throws Exception */ public static void evalScript() throws Exception{ Binding binding = new Binding(); GroovyShell shell = new GroovyShell(binding); //直接方法調用 //shell.parse(new File(//)) Script script = shell.parse("def join(String[] list) {return list.join('--');}"); String joinString = (String)script.invokeMethod("join", new String[]{"A1","B2","C3"}); System.out.println(joinString); ////腳本可以為任何格式,可以為main方法,也可以為普通方法 //1) def call(){...};call(); //2) call(){...}; script = shell.parse("static void main(String[] args){i = i * 2;}"); script.setProperty("i", new Integer(10)); script.run();//運行, System.out.println(script.getProperty("i")); //the same as System.out.println(script.getBinding().getVariable("i")); script = null; shell = null; }
二. GroovyClassLoader代碼示例
1) 解析groovy文件
/** * from source file of *.groovy */ public static void parse() throws Exception{ GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader()); File sourceFile = new File("D:\\TestGroovy.groovy"); Class testGroovyClass = classLoader.parseClass(new GroovyCodeSource(sourceFile)); GroovyObject instance = (GroovyObject)testGroovyClass.newInstance();//proxy Long time = (Long)instance.invokeMethod("getTime", new Date()); System.out.println(time); Date date = (Date)instance.invokeMethod("getDate", time); System.out.println(date.getTime()); //here instance = null; testGroovyClass = null; }
2) 如何加載已經編譯的groovy文件(.class)
public static void load() throws Exception { GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader()); BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\TestGroovy.class")); ByteArrayOutputStream bos = new ByteArrayOutputStream(); for(;;){ int i = bis.read(); if( i == -1){ break; } bos.write(i); } Class testGroovyClass = classLoader.defineClass(null, bos.toByteArray()); //instance of proxy-class //if interface API is in the classpath,you can do such as: //MyObject instance = (MyObject)testGroovyClass.newInstance() GroovyObject instance = (GroovyObject)testGroovyClass.newInstance(); Long time = (Long)instance.invokeMethod("getTime", new Date()); System.out.println(time); Date date = (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) 代碼樣例
public static void evalScript() throws Exception{ ScriptEngineManager factory = new ScriptEngineManager(); //每次生成一個engine實例 ScriptEngine engine = factory.getEngineByName("groovy"); System.out.println(engine.toString()); assert engine != null; //javax.script.Bindings Bindings binding = engine.createBindings(); binding.put("date", new Date()); //如果script文本來自文件,請首先獲取文件內容 engine.eval("def getTime(){return date.getTime();}",binding); engine.eval("def sayHello(name,age){return 'Hello,I am ' + name + ',age' + age;}"); Long time = (Long)((Invocable)engine).invokeFunction("getTime", null); System.out.println(time); String message = (String)((Invocable)engine).invokeFunction("sayHello", "zhangsan",new Integer(12)); System.out.println(message); }
需要提醒的是,在groovy中,${expression} 將會被認為一個變量,如果需要輸出"$"符號,需要轉義為"\$".
關於ScriptEngine更多介紹,請參考.