如何从JavaScript调用Java实例的方法?
我正在使用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
应该是其方法被绑定的实例。
以上只是背景信息。 现在我将解释问题是什么,并为它提供一个自然的解决方案,即允许直接调用实例方法作为全局函数,而不是显式创建对象的实例,然后使用该实例调用方法。
调用FunctionObject.call()
,Rhino调用FunctionObject.call()
方法,该方法传递this
的引用。 如果函数是一个全局函数,则调用它而不引用this
(即xxx()
而不是this.xxx()
),传递给FunctionObject.call()
方法的this
变量的值是调用的范围(即在这种情况下, this
参数的值将与scope
参数的值相同)。
如果调用的java方法是一个实例方法,这就成了一个问题,因为根据FunctionObject
类的构造FunctionObject
的JavaDocs:
如果该方法不是静态的,那么Java this
值将对应于JavaScript this
值。 任何使用不具有正确Java类型的值调用该函数的尝试都将导致错误。
在上面描述的场景中就是这种情况。 javascript this
值与java this
值不对应,并导致不兼容的对象错误。
解决方案是inheritanceFunctionObject
,重写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 实例绑定到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域引用。