Java 代码无法使用新上下文从脚本引擎调用方法
Java code cannot invoke method from scriptengine with new context
我正在尝试从 Java 中的 Java 脚本实现示例调用方法。
private static final String JS = "function doit(p) { list.add(p); return true; }";
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
List<String> list = new ArrayList<>();
ScriptEngineManager scriptManager = new ScriptEngineManager();
ScriptEngine engine = scriptManager.getEngineByName("nashorn");
ScriptContext context = new SimpleScriptContext();
context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
Bindings scope = context.getBindings(ScriptContext.ENGINE_SCOPE);
scope.put("list", list);
engine.eval(JS, context);
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("doit", "Hello!!!");
System.out.println(list.size());
}
}
此代码抛出异常:
Exception in thread "main" java.lang.NoSuchMethodException: No such function doit
at jdk.nashorn.api.scripting.ScriptObjectMirror.callMember(ScriptObjectMirror.java:204)
at jdk.nashorn.api.scripting.NashornScriptEngine.invokeImpl(NashornScriptEngine.java:383)
at jdk.nashorn.api.scripting.NashornScriptEngine.invokeFunction(NashornScriptEngine.java:190)
at testjavascriptinteraction.TestJavaScript.main(TestJavaScript.java:32)
版本
java -version
openjdk version "1.8.0_91"
OpenJDK Runtime Environment (build 1.8.0_91-8u91-b14-3ubuntu1~16.04.1-b14)
OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)
怎么了?
我可以重现这个问题,并且找到了解决方法。
我有一种预感,在投射到 Invocable
之后,引擎自己的脚本上下文仍在使用,而不是您传递给 eval
的临时上下文。
我可以通过在转换为 Invacable
之前使用此上下文调用 engine.setContext(...)
来解决此问题。即:
...
scope.put("list", list);
engine.eval(JS, context); // 'context' now contains the 'doit' function.
engine.setContext(context); // <-- Right here
Invocable invocable = (Invocable) engine; // Now the Invocable will use 'context'.
invocable.invokeFunction("doit", "Hello!!!"); //Runs fine
System.out.println(list.size());
这有效的事实似乎证实了我的直觉。
您的 scope
和 eval()
都在使用自定义 ScriptContext
,但您的 invokeFunction()
不是。该函数注册到上下文,而不是引擎。
选项 1:不使用自定义上下文。
List<String> list = new ArrayList<>();
ScriptEngineManager scriptManager = new ScriptEngineManager();
ScriptEngine engine = scriptManager.getEngineByName("nashorn");
Bindings scope = engine.getBindings(ScriptContext.ENGINE_SCOPE);
scope.put("list", list);
engine.eval("function doit(p) { list.add(p); return true; }");
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("doit", "Hello!!!");
System.out.println(list); // prints: [Hello!!!]
更新
方案二:直接调用注册函数
函数作为具有函数值的普通变量在上下文中注册。这意味着您的 scope
在 eval()
调用后将有一个 doit
变量。
您必须使用 Nashorn class 才能直接调用它,但这很容易。
// same code as above up to eval call
engine.eval("function doit(p) { list.add(p); return true; }", context);
// Instead of invokeFunction(), just get and call function manually
JSObject func = (JSObject)scope.get("doit");
func.call(null, "Hello!!!"); // like a "static" call, no 'this' value
System.out.println(list); // prints: [Hello!!!]
我正在尝试从 Java 中的 Java 脚本实现示例调用方法。
private static final String JS = "function doit(p) { list.add(p); return true; }";
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
List<String> list = new ArrayList<>();
ScriptEngineManager scriptManager = new ScriptEngineManager();
ScriptEngine engine = scriptManager.getEngineByName("nashorn");
ScriptContext context = new SimpleScriptContext();
context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
Bindings scope = context.getBindings(ScriptContext.ENGINE_SCOPE);
scope.put("list", list);
engine.eval(JS, context);
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("doit", "Hello!!!");
System.out.println(list.size());
}
}
此代码抛出异常:
Exception in thread "main" java.lang.NoSuchMethodException: No such function doit
at jdk.nashorn.api.scripting.ScriptObjectMirror.callMember(ScriptObjectMirror.java:204)
at jdk.nashorn.api.scripting.NashornScriptEngine.invokeImpl(NashornScriptEngine.java:383)
at jdk.nashorn.api.scripting.NashornScriptEngine.invokeFunction(NashornScriptEngine.java:190)
at testjavascriptinteraction.TestJavaScript.main(TestJavaScript.java:32)
版本
java -version
openjdk version "1.8.0_91"
OpenJDK Runtime Environment (build 1.8.0_91-8u91-b14-3ubuntu1~16.04.1-b14)
OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)
怎么了?
我可以重现这个问题,并且找到了解决方法。
我有一种预感,在投射到 Invocable
之后,引擎自己的脚本上下文仍在使用,而不是您传递给 eval
的临时上下文。
我可以通过在转换为 Invacable
之前使用此上下文调用 engine.setContext(...)
来解决此问题。即:
...
scope.put("list", list);
engine.eval(JS, context); // 'context' now contains the 'doit' function.
engine.setContext(context); // <-- Right here
Invocable invocable = (Invocable) engine; // Now the Invocable will use 'context'.
invocable.invokeFunction("doit", "Hello!!!"); //Runs fine
System.out.println(list.size());
这有效的事实似乎证实了我的直觉。
您的 scope
和 eval()
都在使用自定义 ScriptContext
,但您的 invokeFunction()
不是。该函数注册到上下文,而不是引擎。
选项 1:不使用自定义上下文。
List<String> list = new ArrayList<>();
ScriptEngineManager scriptManager = new ScriptEngineManager();
ScriptEngine engine = scriptManager.getEngineByName("nashorn");
Bindings scope = engine.getBindings(ScriptContext.ENGINE_SCOPE);
scope.put("list", list);
engine.eval("function doit(p) { list.add(p); return true; }");
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("doit", "Hello!!!");
System.out.println(list); // prints: [Hello!!!]
更新
方案二:直接调用注册函数
函数作为具有函数值的普通变量在上下文中注册。这意味着您的 scope
在 eval()
调用后将有一个 doit
变量。
您必须使用 Nashorn class 才能直接调用它,但这很容易。
// same code as above up to eval call
engine.eval("function doit(p) { list.add(p); return true; }", context);
// Instead of invokeFunction(), just get and call function manually
JSObject func = (JSObject)scope.get("doit");
func.call(null, "Hello!!!"); // like a "static" call, no 'this' value
System.out.println(list); // prints: [Hello!!!]