10

私はMozillaRhinoJavaScriptエミュレーターを使用しています。これにより、Javaメソッドをコンテキストに追加して、JavaScript関数であるかのように呼び出すことができます。しかし、静的メソッドを使用する場合を除いて、それを機能させることはできません。

問題は、ドキュメントのこの部分です。

メソッドが静的でない場合、Javaの「this」値はJavaScriptの「this」値に対応します。正しいJavaタイプではない'this'値を使用して関数を呼び出そうとすると、エラーが発生します。

どうやら、私のJavaの「this」値はJavaScriptの値と対応しておらず、それらを対応させる方法がわかりません。最後に、Javaでインスタンスを作成し、そこからいくつかのメソッドをグローバルスコープにインストールして、Javaからインスタンスを初期化できるようにしますが、スクリプトで使用します。

誰かがこれのためのいくつかのサンプルコードを持っていますか?

4

3 に答える 3

11

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()の値は、呼び出しが行われたスコープです(つまり、この場合はパラメータはパラメータの値と同じになります)。thisFunctionObject.call()thisscope

FunctionObjectこれは、呼び出されるJavaメソッドがインスタンスメソッドである場合に問題になります。これは、クラスのコンストラクタのJavaDocsによると次のためです。

メソッドが静的でない場合、Javathis値はJavaScriptthis値に対応します。this適切なJavaタイプではない値で関数を呼び出そうとすると、エラーが発生します。

そして、上記のシナリオでは、まさにその通りです。javascriptthis値はjavathis値に対応しておらず、互換性のないオブジェクトエラーが発生します。

解決策は、サブクラス化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);

お役に立てれば。

于 2013-05-10T09:59:32.483 に答える
3

できることは、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 6java.util.scriptAPIを使用してJavaとRhinoの間を移動していることを前提としています。「プレーン」なライノとは少し違いますが、基本的な考え方は同じです。

または、JavaクラスをJavascript環境にインポートすることもできます。Javaクラスへの参照でJavascript "new"を使用すると、RhinoはJavaオブジェクトへのJavascriptドメイン参照を提供します。

于 2010-08-09T16:25:43.863 に答える
1

@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
}
于 2020-03-21T00:34:46.917 に答える