最近設計一個數據統計系統,系統中上百種數據統計維度,而且這些數據統計的指標可能隨時會調整.如果基於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應該是推薦的一種使用策略.規範化,而且簡便.
官方參考文檔:http://docs.groovy-lang.org/latest/html/documentation/guide-integrating.html
一.GroovyShell代碼樣例
1) 簡單的表達式執行,方法調用
Java代碼
/**
* 簡答腳本執行
* @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;
}
GroovyShell是一種性能較低的方式,因爲每次都需要創建shell和script,這也意味着每次都需要對expression進行“編譯”(JAVA Class)。
2) 僞main方法執行.
Java代碼
/**
* 當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腳本
Java代碼
/**
* 運行完整腳本
* @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) 方法執行和分部調用
Java代碼
/**
* 以面向"過程"的方式運行腳本
* @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文件
Java代碼
/**
* 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)
Java代碼
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依賴
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) 代碼樣例
Java代碼
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} 將會被認爲一個變量,如果需要輸出"$"符號,需要轉義爲"\$".
這是一種性能較高的方式,engine我們可以聲明爲全局實例,是線程安全的。每次調用時只需要創建新的Binndings即可,此外如果腳本已經編譯過(首次執行之後)其Class將會被緩存,則此後不需要再次編譯。
運行javascript腳本
try {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
Compilable compilable = (Compilable) engine;
Bindings bindings = engine.createBindings(); //Local級別的Binding
//定義函數並調用
String script = "function add(op1,op2){return op1+op2} add(a, b)";
//解析編譯腳本函數
CompiledScript JSFunction = compilable.compile(script);
bindings.put("a", 1);bindings.put("b", 2); //通過Bindings加入參數
Object result = JSFunction.eval(bindings);
System.out.println(result); //調用緩存着的腳本函數對象,Bindings作爲參數容器傳入
}catch (ScriptException e) {
e.printStackTrace();
}
關於ScriptEngine更多介紹,請參考.