我正在使用Mozilla Rhino JavaScript模拟器。它允许我将Java方法添加到上下文中,然后像调用JavaScript函数一样调用它们。但是我无法使用它,除非使用静态方法。
问题是文档的此部分:
如果该方法不是静态的,则Java的“ this”值将对应于JavaScript的“ this”值。任何用不正确的Java类型的'this'值调用该函数的尝试都会导致错误。
显然,我的Java“ this”值与JavaScript中的值不对应,我也不知道如何使它们对应。最后,我想在Java中创建一个实例,并在全局范围内从中安装一些方法,因此我可以从Java初始化该实例,但可以在我的脚本中使用它。
有人为此提供一些示例代码吗?
当要在范围内将java方法(静态或非静态)用作全局函数时,我们使用以下逻辑:
FunctionObject javascriptFunction = new FunctionObject(/* String*/ javascriptFunctionName, /* Method */ javaMethod, /*Scriptable */ parentScope);
boundScope.put(javascriptFunctionName, boundScope, javascriptFunction);
boundScope
始终应该是该功能可用的范围。
然而,父作用域的值取决于我们是绑定实例方法还是静态方法。对于静态方法,可以是任何有意义的范围。它甚至可以与boundScope
相同。
但是在使用实例方法的情况下,parentScope
应该是绑定了方法的实例。
以上只是背景信息。现在,我将解释问题所在,并给出一个自然的解决方案,即允许直接将实例方法作为全局函数调用,而不是显式创建对象的实例,然后使用该实例调用方法的方法。
[调用函数时,Rhino调用FunctionObject.call()
方法,该方法传递了对this
的引用。如果函数是全局函数,则在不引用this
的情况下调用该函数(即xxx()
而不是this.xxx()
),传递给this
方法的FunctionObject.call()
变量的值为进行调用的范围(即,在这种情况下,this
参数的值将与scope
参数的值相同)。
如果被调用的java方法是一个实例方法,这是一个问题,因为根据FunctionObject
类的构造函数的JavaDocs:
如果该方法不是静态的,则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 instance绑定到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和Rhino之间进行转换。与“普通”犀牛不同,但基本思想是相同的。
或者,您可以将Java类导入到Javascript环境中,并且在对Java类的引用中使用Javascript“ new”时,Rhino会为您提供对Java对象的Javascript域引用。
@@ Jawad的答案很好,但是仍然需要parentScope作为您要插入的对象的父级。如果要将全局函数添加到使用initStandardObjects()创建的作用域中,则必须使用共享作用域(已解释为in the docs,但缺少完整的示例)。
这是我的操作方式(这是Android,请原谅Kotlin ::
/**
* Holds the global or "window" objects that mock browser functionality.
*/
internal class Global() {
fun requestAnimationFrame(callback: BaseFunction) {
...
}
fun setTimeout(callback: BaseFunction, delay: Int): Int {
...
}
fun clearTimeout(id: Int) {
...
}
internal fun register(context: Context, scope: Scriptable): Scriptable {
return context.wrapFactory.wrapAsJavaObject(context, scope, this, Global::class.java)
}
}
/**
* Creates the root scope containing the StandardObjects, and adds Global functions to it.
*/
fun createRootScope(): Scriptable {
val context = Context.enter()
val sharedScope = context.initSafeStandardObjects(null, true)
val rootScope = Global().register(context, sharedScope)
rootScope.prototype = sharedScope;
rootScope.parentScope = null;
return rootScope
}