如何从JavaScript调用Java实例的方法?

Aar*_*lla 9 javascript java rhino method-call

我正在使用Mozilla Rhino JavaScript模拟器.它允许我将Java方法添加到上下文中,然后将它们称为JavaScript函数.但除非我使用静态方法,否则我无法工作.

问题是这部分文档:

如果方法不是静态的,那么Java'this'值将对应于JavaScript'this'值.任何使用不具有正确Java类型的'this'值调用该函数的尝试都将导致错误.

显然,我的Java"this"值与JavaScript中的值不对应,我不知道如何使它们对应.最后,我想在Java中创建一个实例,并在全局范围内安装几个方法,因此我可以从Java初始化实例,但在我的脚本中使用它.

有没有人有一些示例代码?

Jaw*_*wad 10

当一个java方法(无论是静态的还是非静态的)作为范围内的全局函数可用时,我们使用以下逻辑:

FunctionObject javascriptFunction = new FunctionObject(/* String*/ javascriptFunctionName, /* Method */ javaMethod, /*Scriptable */ parentScope);
boundScope.put(javascriptFunctionName, boundScope, javascriptFunction);
Run Code Online (Sandbox Code Playgroud)

这里boundScope应该始终是使功能可用的范围.

但是,父作用域的值取决于我们是绑定实例方法还是静态方法.在静态方法的情况下,它可以是任何有意义的范围.它甚至可以和boundScope.

但是在实例方法的情况下,parentScope应该是绑定方法的实例.

以上只是背景信息.现在我将解释问题是什么,并给出一个自然的解决方案,即允许直接调用实例方法作为全局函数,而不是显式创建对象的实例,然后使用该实例调用方法.

调用函数时,Rhino调用FunctionObject.call()传递引用的方法this.如果函数是一个全局函数,则调用它而不引用this(即xxx()代替this.xxx()),this传递给FunctionObject.call()方法的变量的值是调用的范围(即在这种情况下的值)this参数将与参数的值相同scope).

如果被调用的java方法是一个实例方法,这就成了一个问题,因为根据FunctionObject类的构造函数的JavaDocs :

如果方法不是静态的,则Java this值将对应于JavaScript this值.任何使用this非Java类型的值调用该函数的尝试都将导致错误.

在上面描述的场景中就是这种情况.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);
    }
  }
Run Code Online (Sandbox Code Playgroud)

我认为最好用下面粘贴的自包含/完整示例来理解.在此示例中,我们将实例方法: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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

如果要查看修复后的行为,请取消注释第78行和注释第79行:

return super.call(cx, scope, getParentScope(), args);
//return super.call(cx, scope, thisObj, args);
Run Code Online (Sandbox Code Playgroud)

如果你想看到没有修复的行为,那么注释第78行并取消注释第79行:

//return super.call(cx, scope, getParentScope(), args);
return super.call(cx, scope, thisObj, args);
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.