當製成的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);
希望這會有所幫助。
如何從Java實例獲得可用的'parentScope'? – 2013-05-13 08:36:03
我已經用完整的例子更新了答案。希望有所幫助。 – Jawad 2013-05-14 13:42:50
謝謝。接受比我之前得到的更好的答案 – 2013-05-14 14:49:11