如何从JavaScript调用Java实例的方法? [英] How do I call a method of a Java instance from JavaScript?

查看:98
本文介绍了如何从JavaScript调用Java实例的方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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:


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

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.

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

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?

推荐答案

当一个java方法(无论是静态的还是我们使用以下逻辑将作为全局函数提供非静态函数:

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

此处 boundScope 应该始终是范围其中函数可用。

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

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

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.

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

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.

调用函数时,Rhino调用 FunctionObject.call()方法,该方法传递对此。如果函数是一个全局函数,则调用它时不会引用 this (即 xxx()而不是 this.xxx()),传递给变量的值FunctionObject.call()方法是进行调用的范围(即,在这种情况下,参数的值将与范围参数的值)。

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).

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

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:

如果方法不是静态的,那么Java 这个值将对应于JavaScript 值。任何使用值调用该函数的尝试都不会导致错误。

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.

在上面描述的场景中就是这样。 javascript 值与java 值不对应,并导致不兼容的对象错误。

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.

解决方案是子类 FunctionObject ,覆盖 call()方法,强行修复引用,然后让通话正常进行。

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

所以类似:

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)。

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

如果你想看到行为使用修复然后取消注释第78行和注释第79行:

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

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

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

希望这会有所帮助。

这篇关于如何从JavaScript调用Java实例的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆