繁体   English   中英

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

[英]How do I call a method of a Java instance from JavaScript?

我正在使用 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() ),传递给FunctionObject.call()方法的this变量的值是进行调用的范围(即在这种情况下, this参数的值将与scope参数的值相同)。

如果被调用的 java 方法是一个实例方法,这就会成为一个问题,因为根据FunctionObject类的构造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实例绑定到 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 之间切换。 与“普通”Rhino 略有不同,但基本思想是相同的。

或者,您可以将 Java 类导入到 Javascript 环境中,当您对 Java 类的引用使用 Javascript“新”时,Rhino 会为您提供对 Java 对象的 Javascript 域引用。

@Jawad 的回答很好,但它仍然需要 parentScope 是您尝试插入的对象的父级。 如果要将全局函数添加到使用 initStandardObjects() 创建的范围中,则必须使用共享范围( 文档中对此进行了解释但缺少完整示例)。

这是我的做法(这是 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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM