Как мне вызвать метод экземпляра Java из JavaScript? - PullRequest
9 голосов
/ 09 августа 2010

Я использую эмулятор JavaScript Mozilla Rhino.Это позволяет мне добавлять методы Java в контекст, а затем вызывать их, как если бы они были функциями JavaScript.Но я не могу заставить его работать, кроме случаев, когда я использую статический метод.

Проблема заключается в этой части документации:

Если метод не является статическим, JavaЗначение this будет соответствовать значению JavaScript this.Любая попытка вызвать функцию со значением «this», которое не имеет правильного типа Java, приведет к ошибке.

Очевидно, что мое значение Java «this» не соответствует значениюв JavaScript, и я понятия не имею, как заставить их соответствовать.В конце я хотел бы создать экземпляр в Java и установить пару методов из него в глобальной области видимости, чтобы я мог инициализировать экземпляр из Java, но использовать его в своих сценариях.

у кого-нибудь есть пример кода для этого?

Ответы [ 2 ]

9 голосов
/ 10 мая 2013

Когда метод 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()), значение переменной this, которая передается методу FunctionObject.call(), является область, в которой был сделан вызов (т. е. в этом случае значение параметра this будет таким же, как значение параметра scope).

Это становится проблемой в случае, если вызываемый java-метод является методом экземпляра, потому что согласно JavaDocs конструктора FunctionObject класса:

Если метод не является статическим, значение 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);
    }
  }

Я думаю, что это было бы лучше всего понять с помощью отдельного / полного примера, вставленного ниже. В этом примере мы представляем метод экземпляра: 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);

Надеюсь, это поможет.

3 голосов
/ 09 августа 2010

Что вы можете сделать, это связать экземпляр 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 это немного отличается, но основная идея та же.

В качестве альтернативы, вы можете импортировать классы Java в среду Javascript, и Rhino дает вам ссылки Javascript-домена на объекты Java при использованииJavascript "новый" в ссылках на классы Java.

...