在Java中使用腳本語言 javax.script探秘
之前我們提到Java,總說其最大的特點是跨平臺,是一次編寫到處運行。但最近幾年,Java領域最大的變化就是基于JVM的語言正在開始流行,Java已經進入了混合編程時代。今天我們要向您介紹的就是Java在多語言方面的一個嘗試,在Java中使用腳本語言。(關于Java多語言編程請參考51CTO的專題:《Java程序員的未來 走向混合編程時代》)。
1、可用的腳本引擎
Java 6提供對執行腳本語言的支持,這個支持來自于JSR223規范,對應的包是javax.script。默認情況下,Java 6只支持JavaScript腳本,它底層的實現是Mozilla Rhino,它是個純Java的JavaScript實現。可以通過下面的代碼列出當前環境中支持的腳本引擎:
- ScriptEngineManager manager = new ScriptEngineManager();
- ListScriptEngineFactory> factories = manager.getEngineFactories();
- for (ScriptEngineFactory f : factories) {
- System.out.println(
- "egine name:"+f.getEngineName()+
- ",engine version:"+f.getEngineVersion()+
- ",language name:"+f.getLanguageName()+
- ",language version:"+f.getLanguageVersion()+
- ",names:"+f.getNames()+
- ",mime:"+f.getMimeTypes()+
- ",extension:"+f.getExtensions());
- }
輸出結果: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示例:
- ScriptEngineManager manager = new ScriptEngineManager ();
- ScriptEngine engine = manager.getEngineByName ("js");
- String script = "print ('hello script')";
- try {
- engine.eval (script);
- } catch (ScriptException e) {
- e.printStackTrace();
- }
使用的API還是很簡單的,ScriptEngineManager是ScriptEngine的工廠,實例化該工廠的時候會加載可用的所有腳本引擎。從工廠中創建ScriptEngine可以使用getEngineByName、getEngineByExtension或 getEngineByMimeType來得到,只要參數名字能對上。執行腳本調用eval方法即可(效果等同于JavaScript中的eval)。
3、傳遞變量
可以向腳本中傳遞變量,使得Java代碼可以和腳本代碼交互,示例如下:
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("js");
- engine.put("a", 4);
- engine.put("b", 6);
- try {
- Object maxNum = engine.eval("function max_num(a,b){return (a>b)?a:b;}max_num(a,b);");
- System.out.println("max_num:" + maxNum);
- } catch (Exception e) {
- e.printStackTrace();
- }
輸出內容: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可選實現的接口。下面是個使用示例:
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("js");
- try {
- engine.eval("function max_num(a,b){return (a>b)?a:b;}");
- Invocable invoke = (Invocable) engine;
- Object maxNum = invoke.invokeFunction("max_num",4,6);
- System.out.println(maxNum);
- maxNum = invoke.invokeFunction("max_num", 7,6);
- System.out.println(maxNum);
- } catch (Exception e) {
- // TODO: handle exception
- }
上面的invokeFunction,第一個參數調用的腳本函數名,后面跟的可變參數是對應的腳本函數參數。#p#
Invocable還有個很酷的功能,就是動態實現接口,它可以從腳本引擎中得到Java Interface 的實例;也就是說,可以定義個一個Java接口,其實現是由腳本完成。以上面的例子為例,定義接口JSLib,該接口中的函數和JavaScript中的函數簽名保持一致:
- public interface JSLib {
- public int max_num(int a,int b);
- }
調用示例:
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("js");
- try {
- engine.eval("function max_num(a,b){return (a>b)?a:b;}");
- Invocable invoke = (Invocable) engine;
- JSLib jslib = invoke.getInterface(JSLib.class);
- int maxNum = jslib.max_num(4,6);
- System.out.println(maxNum);
- } catch (Exception e) {
- // TODO: handle exception
- }
5、使用Java對象
可以在JavaScript中使用Java代碼,這確實是很酷的事情。在Rhino中,可以通過importClass導入一個類,也可以通過importPackage導入一個包,也可以直接使用全路經的類。在創建對象時,new也不是必須的。示例代碼如下:
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("js");
- try {
- String script = "var list = java.util.ArrayList();list.add(\"kafka0102\");print(list.get(0));";
- engine.eval(script);
- } catch (Exception e) {
- e.printStackTrace();
- }
6、編譯執行
腳本引擎默認是解釋執行的,如果需要反復執行腳本,可以使用它的可選接口Compilable來編譯執行腳本,以獲得更好的性能,示例代碼如下:
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("js");
- try {
- Compilable compEngine = (Compilable) engine;
- CompiledScript script = compEngine.compile("function max_num(a,b){return (a>b)?a:b;}");
- script.eval();
- Invocable invoke = (Invocable) engine;
- Object maxNum = invoke.invokeFunction("max_num",4,6);
- System.out.println(maxNum);
- } catch (Exception e) {
- e.printStackTrace();
- }
7、總結
除了上面提到的特性,腳本引擎還有一些不錯的功能,比如可以執行腳本文件,可以由多線程異步執行腳本等功能。引入腳本引擎,可以對一些配置擴展和業務規則做更強大而靈活的支持,也方便使用者選擇自己熟悉的腳本語言來編寫業務規則等。

















