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

发布于 2024-09-13 17:39:12 字数 404 浏览 7 评论 0原文

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

问题出在文档的这一部分:

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

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

有人有一些示例代码吗?

I'm using the Mozilla Rhino JavaScript emulator. It allows me to add Java methods to a context and then call them as if they were JavaScript functions. But I can't get it to work except when I use a static method.

The problem is this part of the documentation:

If the method is not static, the Java 'this' value will correspond to the JavaScript 'this' value. Any attempt to call the function with a 'this' value that is not of the right Java type will result in an error.

Apparently, my Java "this" value doesn't correspond with the one in JavaScript and I have no idea how to make them correspond. In the end, I'd like to create an instance in Java, and install a couple of methods from it in the global scope, so I can initialize the instance from Java but use it in my scripts.

Does anyone have some example code for this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

黯然 2024-09-20 17:39:12

当一个 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 类的构造函数的 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);

希望这会有所帮助。

When a java method (whether static or non-static) is to be made available as a global function within a scope we use the following logic:

FunctionObject javascriptFunction = new FunctionObject(/* String*/ javascriptFunctionName, /* Method */ javaMethod, /*Scriptable */ parentScope);
boundScope.put(javascriptFunctionName, boundScope, javascriptFunction);

Here the boundScope should always be the scope in which the function is to be made available.

However the value of the parent scope depends on whether we are binding an instance method or static method. In case of a static method, it can be any scope that makes sense. It can even be the same as the boundScope.

But in case of instance method, the parentScope should be the instance whose method is being bound.

The above was just background info. Now I will explain what the issue is and give a natural solution for it i.e. one that allows invoking the instance method directly as a global function rather than explicitly creating an instance of the object and then invoking the method using that instance.

When a function is called, Rhino invokes the FunctionObject.call() method that is passed a reference to this. In case the function is a global function, it is called without a reference to this (i.e. xxx() instead of this.xxx()), the value of the this variable that gets passed to the FunctionObject.call() method is the scope in which the call was made (i.e. in this case the value of the this parameter will be same as the value of the scope parameter).

This becomes a problem in case the java method being invoked is an instance method because as per the JavaDocs of constructor of FunctionObject class:

If the method is not static, the Java this value will correspond to the JavaScript this value. Any attempt to call the function with a this value that is not of the right Java type will result in an error.

And in the scenario described above that is exactly the case. The javascript this value does NOT correspond to the java this value and results in an incompatible object error.

The solution is to subclass FunctionObject, override the call() method, forcefully 'fix' the this reference, and then let the call proceed normally.

So something like:

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);
    }
  }

I think it would be best understood with a self-contained/complete example pasted below. In this example, we are exposing the instance method: myJavaInstanceMethod(Double number) as a global function within a javascript scope ('scriptExecutionScope'). So in this case the value of the 'parentScope' parameter must be an instance of the class that contains this method (i.e. 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);
    }
  }
}

If you want to see the behavior WITH the fix then uncomment line 78 and comment line 79:

return super.call(cx, scope, getParentScope(), args);
//return super.call(cx, scope, thisObj, args);

If you want to see the behavior WITHOUT the fix then comment line 78 and uncomment line 79:

//return super.call(cx, scope, getParentScope(), args);
return super.call(cx, scope, thisObj, args);

Hope this helps.

暗地喜欢 2024-09-20 17:39:12

您可以做的是将 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“new”时,Rhino 会为您提供对 Java 对象的 Javascript 域引用。

What you can do is to bind a Java instance to the Javascript context, and then from Javascript that identifier will be a reference to the "real" Java object. You can then use it to make method calls from Javascript to Java.

Java side:

    final Bindings bindings = engine.createBindings();
    bindings.put("javaObject", new YourJavaClass());
    engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);

Javascript:

    javaObject.methodName("something", "something");

Now that example assumes you're using the JDK 6 java.util.script APIs to get between Java and Rhino. From "plain" Rhino, it's a little different but the basic idea is the same.

Alternatively, you can import Java classes into the Javascript environment, and Rhino gives you Javascript-domain references to Java objects when you use Javascript "new" on references to Java classes.

浅笑依然 2024-09-20 17:39:12

@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
}

@Jawad's answer is great, but it still requires the parentScope to be a parent of the object you're trying to insert. If you want to add global functions into the scope created with initStandardObjects(), you have to use shared scope (which is sort of explained in the docs but lacks a full example).

Here's how I did it (this is Android so please excuse the 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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文