2012年10月2日 星期二

[轉載] javax.script包探秘 javascript與java交互執行

轉載來源

這個包用來和JavaScript進行互操作,比如JAVA類可以調用JavaScript中的方法,而JS也可調用 JAVA中的方法.

1、可用的腳本引擎
Java 6提供對執行腳本語言的支持,這個支持來自於JSR223規範,對應的包是javax.script
預設情況下,Java 6只支持javascript腳本,它底層的實現是Mozilla Rhino,它是個純Java的javascript實現
可以通過下面的代碼列出當前環境中支持的腳本引擎:

// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
// evaluate JavaScript code from String
engine.eval("print('Welocme to java world')");
System.out.println();
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> factories = manager.getEngineFactories();
for (ScriptEngineFactory f : factories) {
    System.out.println("egine name:" + f.getEngineName() + "\n"
            + "engine version:" + f.getEngineVersion() + "\n"
            + "language name:" + f.getLanguageName());
}

結果輸出:
Welocme to java world
egine name:Mozilla Rhino
engine version:1.6 release 2
language name:ECMAScript

可以看到,Java內置只支持javascript一種腳本
但是,只要遵循 JSR223,便可以擴展支持多種腳本語言
可以從https://scripting.dev.java.net/上查找當前已被支持的腳本的第三方庫

2、hello script
示例如上面的代碼所示。
使用的API還是很簡單的,ScriptEngineManager是ScriptEngine的工廠,實例化該工廠的時候會加載可用的所有腳本引擎
從工廠中創建ScriptEngine可以使用getEngineByName、getEngineByExtension或 getEngineByMimeType來得到
只要參數名字能對上。執行腳本調用eval方法即可(效果等同於javascript中的eval)

3、傳遞變量
可以向腳本中傳遞變量,使得Java代碼可以和腳本代碼交互,示例如下:

// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
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可選實現的接口。下面是個使用示例:

// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
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,第一個參數調用的腳本函數名,後面跟的可變參數是對應的腳本函數參數
Invocable還有個很酷的功能,就是動態實現接口,它可以從腳本引擎中得到Java Interface 的實例
也就是說,可以定義個一個Java接口,其實現是由腳本完成
以上面的例子為例,定義接口JSLib,該接口中的函數和javascript中的函數簽名保持一致:

public interface JSLib {
    public int max_num(int a, int b);
}
[java] view plaincopyprint?
// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
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也不是必須的。示例代碼如下:

try {
    String script = "var list = java.util.ArrayList();list.add(\"kafka0102\");print(list.get(0));";
    engine.eval(script);
} catch (Exception e) {
    e.printStackTrace();
}

又例如:

try {
    engine.eval("importPackage(javax.swing);" + "var optionPane = "
            + "  JOptionPane.showMessageDialog(null, 'Hello, world!');");
} catch (ScriptException ex) {
    ex.printStackTrace();
}

6、編譯執行
腳本引擎默認是解釋執行的,如果需要反覆執行腳本,可以使用它的可選接口Compilable來編譯執行腳本,以獲得更好的性能,示例代碼如下:

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、總結
除了上面提到的特性,腳本引擎還有一些不錯的功能
比如可以執行腳本文件,可以由多線程異步執行腳本等功能
引入腳本引擎,可以對一些配置擴展和業務規則做更強大而靈活的支持,也方便使用者選擇自己熟悉的腳本語言來編寫業務規則等

public class JSTest {
    public static void 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裡面的內容
[javascript] view plaincopyprint?
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編程了,還不用再編譯,馬上就可以運行.靈活性豈不是變得更高了嗎?

參考自:
【1】javax.script包探秘 javascript與java交互執行
http://blog.csdn.net/lvshow/article/details/7252236
【2】Scripting for the Java Platform
http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/

沒有留言:

張貼留言