import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.Invocable; import javax.script.ScriptException;
ScriptEngine engine = new ScriptEngineManager() .getEngineByName("javascript"); // 建立上下文變量 Bindings bind = engine.createBindings(); // 加入變量champion值為GSP bind.put("champion", "GSP");
// 綁定上下文,作用域為當前引擎范圍 engine.setBindings(bind, ScriptContext.ENGINE_SCOPE); // 執行腳本(另外還有文件讀取方式 //如:engine.eval(new FileReader("xx.js"))); engine.eval("function sayName(){return champion;}"); // 可有調用方法 Invocable invoke = (Invocable) engine; // 調用方法,此處無參數列表 String result = invoke.invokeFunction("sayName", null).toString(); System.out.println(result);
1、可用的腳本引擎
Java 6提供對執行腳本語言的支持,這個支持來自於JSR223規范,對應的包是javax.script。默認情況下,Java 6只支持javascript腳本,它底層的實現是Mozilla Rhino,它是個純Java的javascript實現。可以通過下面的代碼列出當前環境中支持的腳本引擎:
1.ScriptEngineManager manager = new ScriptEngineManager();
2. ListScriptEngineFactory> factories = manager.getEngineFactories();
3. for (ScriptEngineFactory f : factories) {
4. System.out.println(
5. "egine name:"+f.getEngineName()+
6. ",engine version:"+f.getEngineVersion()+
7. ",language name:"+f.getLanguageName()+
8. ",language version:"+f.getLanguageVersion()+
9. ",names:"+f.getNames()+
10. ",mime:"+f.getMimeTypes()+
11. ",extension:"+f.getExtensions());
12. }
輸出結果:egine name:Mozilla Rhino,engine version:1.6 release 2,language name:ECMAScript,language version:1.6,names:[js, rhino, javascript, javascript, ECMAScript, ecmascript],mime:[application/javascript, application/ecmascript, text/javascript, text/ecmascript],extension:[js]。
可以看到,Java內置只支持javascript一種腳本。但是,只要遵循 JSR223,便可以擴展支持多種腳本語言,可以從https://scripting.dev.java.net/上查找當前已被支持的腳本的第三方庫。
2、hello script
接下來給出在Java中使用javascript的Hello world示例:
13.ScriptEngineManager manager = new ScriptEngineManager ();
14. ScriptEngine engine = manager.getEngineByName ("js");
15. String script = "print ('hello script')";
16. try {
17. engine.eval (script);
18. } catch (ScriptException e) {
19. e.printStackTrace();
20. }
使用的API還是很簡單的,ScriptEngineManager是ScriptEngine的工廠,實例化該工廠的時候會加載可用的所有腳本引擎。從工廠中創建ScriptEngine可以使用getEngineByName、getEngineByExtension或 getEngineByMimeType來得到,只要參數名字能對上。執行腳本調用eval方法即可(效果等同於javascript中的eval)。
3、傳遞變量
可以向腳本中傳遞變量,使得Java代碼可以和腳本代碼交互,示例如下:
21.ScriptEngineManager manager = new ScriptEngineManager();
22. ScriptEngine engine = manager.getEngineByName("js");
23. engine.put("a", 4);
24. engine.put("b", 6);
25. try {
26. Object maxNum = engine.eval("function max_num(a,b){return (a>b)?a:b;}max_num(a,b);");
27. System.out.println("max_num:" + maxNum);
28. } catch (Exception e) {
29. e.printStackTrace();
30. }
輸出內容:max_num:6
對於上面put的變量,它作用於自身engine范圍內,也就是ScriptContext.ENGINE_SCOPE,put 的變量放到一個叫Bindings的Map中,可以通過 engine.getBindings(ScriptContext.ENGINE_SCOPE).get(“a”);得到put的內容。和 ENGINE_SCOPE相對,還有個ScriptContext.GLOBAL_SCOPE 作用域,其作用的變量是由同一ScriptEngineFactory創建的所有ScriptEngine共享的全局作用域。
4、動態調用
上面的例子中定義了一個javascript函數max_num,可以通過Invocable接口來多次調用腳本庫中的函數,Invocable接口是 ScriptEngine可選實現的接口。下面是個使用示例:
31.ScriptEngineManager manager = new ScriptEngineManager();
32. ScriptEngine engine = manager.getEngineByName("js");
33. try {
34. engine.eval("function max_num(a,b){return (a>b)?a:b;}");
35. Invocable invoke = (Invocable) engine;
36. Object maxNum = invoke.invokeFunction("max_num",4,6);
37. System.out.println(maxNum);
38. maxNum = invoke.invokeFunction("max_num", 7,6);
39. System.out.println(maxNum);
40. } catch (Exception e) {
41. // TODO: handle exception
42. }
上面的invokeFunction,第一個參數調用的腳本函數名,后面跟的可變參數是對應的腳本函數參數。
Invocable還有個很酷的功能,就是動態實現接口,它可以從腳本引擎中得到Java Interface 的實例;也就是說,可以定義個一個Java接口,其實現是由腳本完成。以上面的例子為例,定義接口JSLib,該接口中的函數和javascript中的函數簽名保持一致:
1.public interface JSLib {
2. public int max_num(int a,int b);
3. }
調用示例:
4.ScriptEngineManager manager = new ScriptEngineManager();
5. ScriptEngine engine = manager.getEngineByName("js");
6. try {
7. engine.eval("function max_num(a,b){return (a>b)?a:b;}");
8. Invocable invoke = (Invocable) engine;
9. JSLib jslib = invoke.getInterface(JSLib.class);
10. int maxNum = jslib.max_num(4,6);
11. System.out.println(maxNum);
12. } catch (Exception e) {
13. // TODO: handle exception
14. }
5、使用Java對象
可以在javascript中使用Java代碼,這確實是很酷的事情。在Rhino中,可以通過importClass導入一個類,也可以通過importPackage導入一個包,也可以直接使用全路經的類。在創建對象時,new也不是必須的。示例代碼如下:
15.ScriptEngineManager manager = new ScriptEngineManager();
16. ScriptEngine engine = manager.getEngineByName("js");
17. try {
18. String script = "var list = java.util.ArrayList();list.add(\"kafka0102\");print(list.get(0));";
19. engine.eval(script);
20. } catch (Exception e) {
21. e.printStackTrace();
22. }
6、編譯執行
腳本引擎默認是解釋執行的,如果需要反復執行腳本,可以使用它的可選接口Compilable來編譯執行腳本,以獲得更好的性能,示例代碼如下:
23.ScriptEngineManager manager = new ScriptEngineManager();
24. ScriptEngine engine = manager.getEngineByName("js");
25. try {
26. Compilable compEngine = (Compilable) engine;
27. CompiledScript script = compEngine.compile("function max_num(a,b){return (a>b)?a:b;}");
28. script.eval();
29. Invocable invoke = (Invocable) engine;
30. Object maxNum = invoke.invokeFunction("max_num",4,6);
31. System.out.println(maxNum);
32. } catch (Exception e) {
33. e.printStackTrace();
34. }
7、總結
除了上面提到的特性,腳本引擎還有一些不錯的功能,比如可以執行腳本文件,可以由多線程異步執行腳本等功能。引入腳本引擎,可以對一些配置擴展和業務規則做更強大而靈活的支持,也方便使用者選擇自己熟悉的腳本語言來編寫業務規則等
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.swing.JFrame;
/**
*
* @author hadeslee
*/
publicclass Test {
publicstaticvoid main(String[] args)throws Exception {
//根據js的后綴名生成一個解析JS的腳本解析引擎
ScriptEngine engin=new ScriptEngineManager().getEngineByExtension("js");
//查詢一下這個引擎是否實現了下面很實用的接口
System.out.println(engin instanceof Invocable);
//聲明兩個對象,傳入到JS里面去
JFrame jf=new JFrame("test");
List list=new ArrayList();
//得到挷定的鍵值對象,把當前的兩個JAVA對象放進去
Bindings bind=engin.createBindings();
bind.put("jf",jf);
bind.put("list",list);
//把挷下的鍵值對象放進去,作用域是當前引擎的范圍
engin.setBindings(bind, ScriptContext.ENGINE_SCOPE);
//用引擎執行一段寫在JS文件里面的代碼
Object obj=engin.eval(new FileReader("test.js"));
//這個時候返回值當然 是null了
System.out.println(obj);
//把當前的引擎強制轉為Invocable,這樣就可以調用定義在JS文件里面的一個一個函數了
Invocable in=(Invocable)engin;
//得到了從JS里面返回來的對象
List l=(List)in.invokeFunction("getNames");
System.out.println(l);
//調用一下定義在JS里面的另一個函數
in.invokeFunction("testJS");
//最后調用一個函數,該函數可以使我們前面定義的窗體顯示出來
in.invokeFunction("doSth");
}
}
下面是定義在test.js里面的內容
function doSth(){ jf.setSize(500,300); jf.setVisible(true); jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE); } function getNames(){ list.add("doSth"); list.add("getNames"); return list; } function testJS(){ print('Hello world!'); }
我們可以看到,在JAVA運行了以后,窗體會顯示出來,並且我們可以接收到從JS解析引擎里面傳回的數據,當然我們也可以調用一個很普通的JS函數,想象一下,如果我們把我們程序運行時的一些對象都設到Bindings里面去,那么我們JS豈不是有很大的自由度了嗎?因為JS里面也可以操作我們的Java對象了,並且我們可以像ava編程一樣的對JS編程了,還不用再編譯,馬上就可以運行.靈活性豈不是變得更高了嗎?