如何从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域引用。