使用Nashorn JavaScript引擎(Java 8),访问ScriptContext变量

4

我在Java中使用了以下代码,其中包含Rhino JavaScript引擎:

@Test
public void testRhino() throws ScriptException {
    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("rhino");
    final String raw = "I am the raw value injected";
    final ScriptContext ctx = new SimpleScriptContext();
    ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);

    String script = "var result = 'I am a result';";
    script += "java.lang.System.out.println(raw);";
    script += "'I am a returned value';";

    final Object res = engine.eval(script, ctx);
    System.out.println(ctx.getAttribute("result"));
    System.out.println(res);
}

脚本的输出(使用 Rhino)为:
I am the raw value injected
I am a result
I am a returned value

Nashorn JavaScript 引擎中,我无法得到 result 的值:

@Test
public void testNashorn() throws ScriptException {
    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("nashorn");
    final String raw = "I am the raw value injected";
    final ScriptContext ctx = new SimpleScriptContext();
    ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);

    String script = "var result = 'I am a result';";
    script += "java.lang.System.out.println(raw);";
    script += "'I am a returned value';";

    final Object res = engine.eval(script, ctx);
    System.out.println(ctx.getAttribute("result"));
    System.out.println(res);
}

返回值

I am the raw value injected
null
I am a returned value

我该如何使用nashorn引擎访问ScriptContext的result变量的值?
2个回答

5
如果您使用ScriptEngine.createEngine API创建ENGINE_SCOPE Bindings,它将按预期工作:
import javax.script.*;

public class Main {
  public static void main(String[] args) throws Exception {

    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("nashorn");
    final String raw = "I am the raw value injected";
    final ScriptContext ctx = new SimpleScriptContext();

    // **This is the inserted line**
    ctx.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);

    ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);

    String script = "var result = 'I am a result';";
    script += "java.lang.System.out.println(raw);";
    script += "'I am a returned value';";

    final Object res = engine.eval(script, ctx);
    System.out.println(ctx.getAttribute("result"));
    System.out.println(res);
 }
}

NashornScriptEngine#createBindings() 方法返回一个 new SimpleBindings(),如果全局引擎被设置,则返回 createGlobalMirror()。无论是使用 -Dnashorn.args=--global-per-engine 运行,还是在创建引擎之前添加 System.setProperty("nashorn.args", "--global-per-engine"); 这一行,都会导致 ctx.getAttribute("result") 再次返回 null。解决 Nashorn 全局问题是将 Nashorn 嵌入 Java 应用程序时最大的痛点之一。 - AJNeufeld

3
Nashorn将存储在ScriptContext中的Bindings视为“只读”。任何试图设置存储在Bindings对象中的变量(或创建新变量)的尝试都会导致在nashorn.global中创建一个新变量,该变量会遮盖该名称的Bindings参数。您可以使用引擎来“评估”变量,使用以下代码:
System.out.println( engine.eval("result", ctx) );

然而,这种方法非常丑陋。 "result"首先被编译成脚本,然后该脚本被评估以返回变量的值。虽然适用于测试,但对于一般解决方案来说可能效率有点低。

更好但也许更脆弱的方法是提取"nashorn.global"变量,并查询所需的值。

Bindings nashorn_global = (Bindings) ctx.getAttribute("nashorn.global");
System.out.println( nashorn_global.get("result") );

另请参阅我在Capturing Nashorn's Global Variables中的hack/答案,以自动将nashorn.global的值移回Map<String,Object>


可以使用ScriptEngine.createBindings API创建一个新的Bindings对象。这将创建一个绑定,直接由Nashorn的全局实例支持 - 反映了对JS全局范围的读取/写入。 - A. Sundararajan
如果设置了-Dnashorn.args=--global-per-engine,即使使用ScriptEngine.createBindings(),问题仍然存在。 - AJNeufeld
使用 --globals-per-engine 选项,每个引擎只有一个基础的 JS/Nashorn 全局对象。所有 ENGINE_SCOPE 绑定将共享相同的底层 Nashorn 全局实例!正如您在上面观察到的那样,在 --global-per-engine 情况下,engine.createBindings() 只会返回一个 SimpleBindings(而不是由新的 Nashorn 全局实例支持的 Bindings)。 - A. Sundararajan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接