【问题标题】:How do I call a method of a Java instance from JavaScript?如何从 JavaScript 调用 Java 实例的方法?
【发布时间】:2011-03-27 09:32:18
【问题描述】:

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

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

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

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

有人有这方面的示例代码吗?

【问题讨论】:

    标签: java javascript rhino method-call


    【解决方案1】:

    您可以做的是将一个 Java instance 绑定到 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 域引用。

    【讨论】:

    • 谢谢,这行得通。由于我直接使用 Rhino API,代码为:global.defineProperty( "javaObject", javaObject, ScriptableObject.CONST );(虽然我不确定CONST - 但其他属性应用更少)。
    • 如果我想使用像 [[1,3],[4,5],[6,9]] 这样的多维整数数组来调用函数,那么如何将 Object[] 作为参数传递?
    【解决方案2】:

    当一个 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);
    

    希望这会有所帮助。

    【讨论】:

    • 如何从 Java 实例中获取可用的parentScope
    • 我已经用一个完整的例子更新了答案。希望对您有所帮助。
    • 谢谢。接受比以前更好的答案
    • 我想这意味着我不能将java.lang.String 的实例放在脚本的上下文中。我总是必须将它包装在扩展 FunctionObject 的东西中。
    • 感谢@Jawad 的精彩回答。你拯救了我的一天。我落后了两周。脱帽
    【解决方案3】:

    @Jawad 的回答很好,但它仍然要求 parentScope 是您尝试插入的对象的父级。如果要将全局函数添加到使用 initStandardObjects() 创建的作用域中,则必须使用共享作用域(这有点解释 in the docs 但缺少完整示例)。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-30
      • 1970-01-01
      • 2015-11-13
      • 1970-01-01
      • 1970-01-01
      • 2017-04-10
      • 2020-03-23
      • 1970-01-01
      相关资源
      最近更新 更多