在Java中使用腳本語(yǔ)言 javax.script探秘
之前我們提到Java,總說(shuō)其最大的特點(diǎn)是跨平臺(tái),是一次編寫(xiě)到處運(yùn)行。但最近幾年,Java領(lǐng)域最大的變化就是基于JVM的語(yǔ)言正在開(kāi)始流行,Java已經(jīng)進(jìn)入了混合編程時(shí)代。今天我們要向您介紹的就是Java在多語(yǔ)言方面的一個(gè)嘗試,在Java中使用腳本語(yǔ)言。(關(guān)于Java多語(yǔ)言編程請(qǐng)參考51CTO的專題:《Java程序員的未來(lái) 走向混合編程時(shí)代》)。
1、可用的腳本引擎
Java 6提供對(duì)執(zhí)行腳本語(yǔ)言的支持,這個(gè)支持來(lái)自于JSR223規(guī)范,對(duì)應(yīng)的包是javax.script。默認(rèn)情況下,Java 6只支持JavaScript腳本,它底層的實(shí)現(xiàn)是Mozilla Rhino,它是個(gè)純Java的JavaScript實(shí)現(xiàn)??梢酝ㄟ^(guò)下面的代碼列出當(dāng)前環(huán)境中支持的腳本引擎:
- 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());
 - }
 
輸出結(jié)果: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內(nèi)置只支持JavaScript一種腳本。但是,只要遵循 JSR223,便可以擴(kuò)展支持多種腳本語(yǔ)言,可以從https://scripting.dev.java.net/上查找當(dāng)前已被支持的腳本的第三方庫(kù)。
2、hello script
接下來(lái)給出在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還是很簡(jiǎn)單的,ScriptEngineManager是ScriptEngine的工廠,實(shí)例化該工廠的時(shí)候會(huì)加載可用的所有腳本引擎。從工廠中創(chuàng)建ScriptEngine可以使用getEngineByName、getEngineByExtension或 getEngineByMimeType來(lái)得到,只要參數(shù)名字能對(duì)上。執(zhí)行腳本調(diào)用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();
 - }
 
輸出內(nèi)容:max_num:6
對(duì)于上面put的變量,它作用于自身engine范圍內(nèi),也就是ScriptContext.ENGINE_SCOPE,put 的變量放到一個(gè)叫Bindings的Map中,可以通過(guò) engine.getBindings(ScriptContext.ENGINE_SCOPE).get(“a”);得到put的內(nèi)容。和ENGINE_SCOPE相對(duì),還有個(gè)ScriptContext.GLOBAL_SCOPE 作用域,其作用的變量是由同一ScriptEngineFactory創(chuàng)建的所有ScriptEngine共享的全局作用域。
4、動(dòng)態(tài)調(diào)用
上面的例子中定義了一個(gè)JavaScript函數(shù)max_num,可以通過(guò)Invocable接口來(lái)多次調(diào)用腳本庫(kù)中的函數(shù),Invocable接口是 ScriptEngine可選實(shí)現(xiàn)的接口。下面是個(gè)使用示例:
- 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,第一個(gè)參數(shù)調(diào)用的腳本函數(shù)名,后面跟的可變參數(shù)是對(duì)應(yīng)的腳本函數(shù)參數(shù)。#p#
Invocable還有個(gè)很酷的功能,就是動(dòng)態(tài)實(shí)現(xiàn)接口,它可以從腳本引擎中得到Java Interface 的實(shí)例;也就是說(shuō),可以定義個(gè)一個(gè)Java接口,其實(shí)現(xiàn)是由腳本完成。以上面的例子為例,定義接口JSLib,該接口中的函數(shù)和JavaScript中的函數(shù)簽名保持一致:
- public interface JSLib {
 - public int max_num(int a,int b);
 - }
 
調(diào)用示例:
- 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對(duì)象
可以在JavaScript中使用Java代碼,這確實(shí)是很酷的事情。在Rhino中,可以通過(guò)importClass導(dǎo)入一個(gè)類,也可以通過(guò)importPackage導(dǎo)入一個(gè)包,也可以直接使用全路經(jīng)的類。在創(chuàng)建對(duì)象時(shí),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、編譯執(zhí)行
腳本引擎默認(rèn)是解釋執(zhí)行的,如果需要反復(fù)執(zhí)行腳本,可以使用它的可選接口Compilable來(lái)編譯執(zhí)行腳本,以獲得更好的性能,示例代碼如下:
- 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、總結(jié)
除了上面提到的特性,腳本引擎還有一些不錯(cuò)的功能,比如可以執(zhí)行腳本文件,可以由多線程異步執(zhí)行腳本等功能。引入腳本引擎,可以對(duì)一些配置擴(kuò)展和業(yè)務(wù)規(guī)則做更強(qiáng)大而靈活的支持,也方便使用者選擇自己熟悉的腳本語(yǔ)言來(lái)編寫(xiě)業(yè)務(wù)規(guī)則等。















 
 
 
 
 
 
 