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
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);
お役に立てれば。