2010-08-09 46 views
8

我正在使用Mozilla Rhino JavaScript模擬器。它允許我將Java方法添加到上下文中,然後將它們稱爲JavaScript函數。但是除非我使用靜態方法,否則我無法使其工作。如何從JavaScript調用Java實例的方法?

的問題是文檔的這一部分:

如果該方法不是靜態的,Java的「這個」值對應的JavaScript「這個」值。任何嘗試使用不正確的Java類型的「this」值調用該函數都會導致錯誤。

顯然,我的Java「this」的值與JavaScript中的不一致,我不知道如何使它們相對應。最後,我想用Java創建一個實例,並在全局範圍內安裝幾個方法,以便從Java初始化實例,但在腳本中使用它。

有沒有人有這樣的例子代碼?

回答

8

當製成的Java方法(無論是靜態或非靜態)是可用的,因爲我們使用下面的邏輯的範圍內,一個全局函數:

FunctionObject javascriptFunction = new FunctionObject(/* String*/ javascriptFunctionName, /* Method */ javaMethod, /*Scriptable */ parentScope); 
boundScope.put(javascriptFunctionName, boundScope, javascriptFunction); 

這裏boundScope應始終處於範圍該功能將被提供。

但是父範圍的值取決於我們是綁定實例方法還是靜態方法。在靜態方法的情況下,它可以是任何有意義的範圍。它甚至可以與boundScope相同。

但是在實例方法的情況下,parentScope應該是其方法被綁定的實例。

以上只是背景資料。現在我將解釋什麼是問題,並給出一個自然的解決方案,即允許直接調用實例方法作爲全局函數,而不是顯式創建對象的實例,然後使用該實例調用方法。

當一個函數被調用時,Rhino會調用傳遞給this的引用的FunctionObject.call()方法。如果該函數是全局函數,則在不參考this(即xxx()而不是this.xxx())的情況下調用該函數,則傳遞給FunctionObject.call()方法的this變量的值是進行調用的範圍(即在這種情況下參數this的值將與參數scope的值相同)。

這情況下被調用的Java方法成爲一個問題是一個實例方法,因爲按照FunctionObject類的構造函數的JavaDoc:

如果該方法不是靜態的,在Java this值將對應的JavaScript值爲this。任何嘗試使用不正確的Java類型的this值調用該函數都會導致錯誤。

而在上述情況下,情況恰恰如此。 javascript this值不對應於java this值,並導致不兼容的對象錯誤。

解決的辦法是將子類FunctionObject覆蓋call()方法,強制「修復」this引用,然後讓該調用正常進行。

因此,像:

FunctionObject javascriptFunction = new MyFunctionObject(javascriptFunctionName, javaMethod, parentScope); 
boundScope.put(javascriptFunctionName, boundScope, javascriptFunction); 


private static class MyFunctionObject extends FunctionObject { 

    private MyFunctionObject(String name, Member methodOrConstructor, Scriptable parentScope) { 
     super(name, methodOrConstructor, parentScope); 
    } 

    @Override 
    public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { 
     return super.call(cx, scope, getParentScope(), args); 
    } 
    } 

我認爲它會與下面粘貼一個包含自/完整的例子可以更好地理解。在這個例子中,我們將實例方法myJavaInstanceMethod(Double number)作爲JavaScript範圍內的全局函數('scriptExecutionScope')公開。因此,在這種情況下,'parentScope'參數的值必須是包含此方法的類的實例(即MyScriptable)。

package test; 

import org.mozilla.javascript.*; 

import java.lang.reflect.Member; 
import java.lang.reflect.Method; 

//-- This is the class whose instance method will be made available in a JavaScript scope as a global function. 
//-- It extends from ScriptableObject because instance methods of only scriptable objects can be directly exposed 
//-- in a js scope as a global function. 
public class MyScriptable extends ScriptableObject { 

    public static void main(String args[]) throws Exception { 

    Context.enter(); 
    try { 
     //-- Create a top-level scope in which we will execute a simple test script to test if things are working or not. 
     Scriptable scriptExecutionScope = new ImporterTopLevel(Context.getCurrentContext()); 
     //-- Create an instance of the class whose instance method is to be made available in javascript as a global function. 
     Scriptable myScriptable = new MyScriptable(); 
     //-- This is not strictly required but it is a good practice to set the parent of all scriptable objects 
     //-- except in case of a top-level scriptable. 
     myScriptable.setParentScope(scriptExecutionScope); 

     //-- Get a reference to the instance method this is to be made available in javascript as a global function. 
     Method scriptableInstanceMethod = MyScriptable.class.getMethod("myJavaInstanceMethod", new Class[]{Double.class}); 
     //-- Choose a name to be used for invoking the above instance method from within javascript. 
     String javascriptFunctionName = "myJavascriptGlobalFunction"; 
     //-- Create the FunctionObject that binds the above function name to the instance method. 
     FunctionObject scriptableInstanceMethodBoundJavascriptFunction = new MyFunctionObject(javascriptFunctionName, 
       scriptableInstanceMethod, myScriptable); 
     //-- Make it accessible within the scriptExecutionScope. 
     scriptExecutionScope.put(javascriptFunctionName, scriptExecutionScope, 
       scriptableInstanceMethodBoundJavascriptFunction); 

     //-- Define a simple test script to test if things are working or not. 
     String testScript = "function simpleJavascriptFunction() {" + 
       " try {" + 
       " result = myJavascriptGlobalFunction(12.34);" + 
       " java.lang.System.out.println(result);" + 
       " }" + 
       " catch(e) {" + 
       " throw e;" + 
       " }" + 
       "}" + 
       "simpleJavascriptFunction();"; 

     //-- Compile the test script. 
     Script compiledScript = Context.getCurrentContext().compileString(testScript, "My Test Script", 1, null); 
     //-- Execute the test script. 
     compiledScript.exec(Context.getCurrentContext(), scriptExecutionScope); 
    } catch (Exception e) { 
     throw e; 
    } finally { 
     Context.exit(); 
    } 
    } 

    public Double myJavaInstanceMethod(Double number) { 
    return number * 2.0d; 
    } 

    @Override 
    public String getClassName() { 
    return getClass().getName(); 
    } 

    private static class MyFunctionObject extends FunctionObject { 

    private MyFunctionObject(String name, Member methodOrConstructor, Scriptable parentScope) { 
     super(name, methodOrConstructor, parentScope); 
    } 

    @Override 
    public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { 
     return super.call(cx, scope, getParentScope(), args); 
//  return super.call(cx, scope, thisObj, args); 
    } 
    } 
} 

如果你想看到的修復行爲則取消註釋線78和註釋行79:

return super.call(cx, scope, getParentScope(), args); 
//return super.call(cx, scope, thisObj, args); 

如果你想看到一個沒有修復的行爲,然後註釋行78和取消註釋行79:

//return super.call(cx, scope, getParentScope(), args); 
return super.call(cx, scope, thisObj, args); 

希望這會有所幫助。

+0

如何從Java實例獲得可用的'parentScope'? – 2013-05-13 08:36:03

+0

我已經用完整的例子更新了答案。希望有所幫助。 – Jawad 2013-05-14 13:42:50

+0

謝謝。接受比我之前得到的更好的答案 – 2013-05-14 14:49:11

3

您可以做的是將Java 實例綁定到Javascript上下文,然後從Javascript中將該標識符引用爲「真實」Java對象。然後,您可以使用它來進行從Javascript到Java的方法調用。

Java方面:

final Bindings bindings = engine.createBindings(); 
    bindings.put("javaObject", new YourJavaClass()); 
    engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE); 

的Javascript:

javaObject.methodName("something", "something"); 

現在示例假設您正在使用JDK 6 java.util.script API的Java和犀牛之間獲得。從「普通」犀牛,它有點不同,但基本的想法是一樣的。

或者,您可以將Java類導入到Javascript環境中,並且當您對Java類的引用使用Javascript「new」時,Rhino會爲您提供Java對象的Javascript-domain引用。

+0

謝謝,這是有效的。由於我直接使用Rhino API,代碼是:'global.defineProperty(「javaObject」,javaObject,ScriptableObject.CONST);'(儘管我不確定CONST是什麼 - 但其他屬性的應用更少) 。 – 2010-08-10 07:50:31

+0

如果我想調用像[[1,3],[4,5],[6,9]]這樣的多維整數數組的函數,那麼我怎麼能傳遞對象[]作爲參數? – 2016-08-11 16:00:58