当Java方法(无论是静态的还是非静态的)都作为一个全局函数在一个范围内可用时,我们使用以下逻辑:
FunctionObject javascriptFunction = new FunctionObject(/* String*/ javascriptFunctionName, /* Method */ javaMethod, /*Scriptable */ parentScope);
boundScope.put(javascriptFunctionName, boundScope, javascriptFunction);
这里
boundScope
应始终是使功能可用的范围。
然而,父作用域的值取决于我们是绑定实例方法还是静态方法。对于静态方法,它可以是任何有意义的范围。它甚至可以和
边界范围
.
但在实例方法中,
parentScope
应为其方法正被绑定的实例。
以上只是背景信息。现在我将解释问题是什么,并给出一个自然的解决方案,即允许直接作为全局函数调用实例方法,而不是显式地创建对象的实例,然后使用该实例调用方法。
调用函数时,Rhino调用
FunctionObject.call()
传递了对的引用的方法
this
. 如果函数是全局函数,则调用它时不引用
这
(即
xxx()
而不是
this.xxx()
)的值
这
传递给
函数对象。调用()
方法是进行调用的范围(在本例中,是
这
参数将与
scope
参数)。
如果调用的Java方法是一个实例方法,那么这将成为一个问题,因为每个构造函数的JavaDoc都是
FunctionObject
班级:
如果该方法不是静态的,则
这
值将对应于javascript
这
价值。任何使用
这
不是正确的Java类型的值会导致错误。
在上面描述的场景中,情况正是如此。JavaScript
这
值与Java不对应
这
值并导致不兼容的对象错误。
解决方案是子类
功能对象
重写
call()
方法,强制“修复”
这
参考,然后让呼叫正常进行。
比如:
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(双编号)公开为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);
希望这有帮助。