【问题标题】:prototype changes from globalscope to enginescope in nashorn原型从 globalscope 更改为 nashorn 中的 enginescope
【发布时间】:2020-03-31 08:40:36
【问题描述】:

我正在尝试将一些库预加载到全局范围内(例如 chai.js)。这改变了一些对象的原型,我意识到这适用于 ENGINE_SCOPE,但不适用于 GLOBAL_SCOPE。

小例子:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
Bindings globalBindings = engine.createBindings();
engine.eval("Object.prototype.test = function(arg){print(arg);}", globalBindings);

//works as expected, printing "hello"
engine.getContext().setBindings(globalBindings, ScriptContext.ENGINE_SCOPE);
engine.eval("var x = {}; x.test('hello');");

//throws TypeError: null is not a function in <eval> at line number 1
engine.getContext().setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
engine.getContext().setBindings(globalBindings, ScriptContext.GLOBAL_SCOPE);
engine.eval("var x = {}; x.test('hello');");

是否有解决方法使其按预期工作,即更改从全局正确传播到引擎范围?

【问题讨论】:

  • 您使用的是哪个 java 版本和 JVM 实现?自首次添加以来(我相信在 Java 7 中),Java 中的脚本在 Java 发布版本的过程中经历了几次变化。
  • 我使用的是 Java 11 和 Liberica jdk,我相信是 openjdk
  • 你知道nashorn is deprecated in JDK 11 - 虽然我不知道这是否是你的问题的原因。
  • 我是。我用我的应用程序运送我自己的 jdk,所以没关系
  • 我想你想运行几个脚本,每次都加载库,但以前的执行没有留下任何东西?例如,如果执行 1 创建了变量 x,那么在执行 2 开始时,您不希望 x 存在吗?

标签: java nashorn scriptengine


【解决方案1】:

为什么您的代码不起作用

全局作用域只能用于简单的变量映射。例如:

ScriptContext defCtx = engine.getContext();
defCtx.getBindings(ScriptContext.GLOBAL_SCOPE).put("foo", "hello");

Object 存在于引擎范围内,因此甚至不会在全局范围内搜索与其相关的任何映射(在您的情况下为 Object.prototype.test)。

文档摘录:

默认上下文的 ENGINE_SCOPE 是 ECMAScript“全局”对象的包装实例 - 这是顶级脚本表达式中的“this”。因此,您可以从此范围对象访问 ECMAScript 顶级对象,例如“Object”、“Math”、“RegExp”、“undefined”。 Nashorn 全局范围对象由一个名为 jdk.nashorn.internal.objects.Global 的内部实现类表示。此类的实例被包装为 jdk.nashorn.api.scripting.ScriptObjectMirror 实例。 ScriptObjectMirror 类实现 javax.script.Bindings 接口。 请注意上下文的 GLOBAL_SCOPE 绑定和 nashorn 全局对象是不同的。 Nashorn 的全局对象与 ENGINE_SCOPE 相关联,而不与 GLOBAL_SCOPE 相关联。 默认脚本上下文的 GLOBAL_SCOPE 对象是一个 javax.script.SimpleBindings 实例。用户可以使用 java 代码中的名称、值对来填充它。

https://wiki.openjdk.java.net/display/Nashorn/Nashorn+jsr223+engine+notes

解决方案

  1. 继续使用引擎作用域
  2. 通过在 Java 命令行中指定 -Dnashorn.args=--global-per-engine 来使用 --global-per-engine option。然后 Nashorn 将为所有脚本评估使用全局对象的单个实例,而不管传递的 ScriptContext 是什么。
  3. 使用成熟的 ScriptContext 而不是 Bindings:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
SimpleScriptContext context = new SimpleScriptContext();
engine.eval("Object.prototype.test = function(arg){print(arg);}", context);
engine.eval("var x = {}; x.test('hello');", context);

如何在每次加载库的情况下运行多个脚本,但之前的执行没有留下任何内容

每当您需要一个新的库上下文时,只需创建它:

public static void main(String[] args) throws ScriptException {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
    SimpleScriptContext context1 = createContextWithLibraries(engine);
    //works as expected, printing "hello"
    engine.eval("var x = {}; x.test('hello'); var y = 'world';", context1);
    SimpleScriptContext context2 = createContextWithLibraries(engine);
    //works as expected, printing "hello"
    engine.eval("var x = {}; x.test('hello');", context2);
    //works as expected, printing "world"
    engine.eval("print(y);", context1);
    //fails with exception since there is no "y" variable in context2
    engine.eval("print(y);", context2);
}

private static SimpleScriptContext createContextWithLibraries(ScriptEngine engine) throws ScriptException {
    SimpleScriptContext context = new SimpleScriptContext();
    engine.eval("Object.prototype.test = function(arg){print(arg);}", context);
    return context;
}

【讨论】:

  • 感谢您的广泛回答,尽管我希望有一个不同的解决方案,因为我想避免重新评估。我将使用 Oliver 的解决方案来缓存已编译的代码,但我接受您的回答,因为它指出了实际参考以及这不起作用的原因。
【解决方案2】:

丹尼斯已经解释了这个问题,所以我不会重复他所说的。但是,我将给出另一种解决方案。为了避免一遍又一遍地解析相同库的开销,您可以编译它们。这是一种方法:

import java.util.*;
import javax.script.*;

public class Test
{
    public static List<CompiledScript> compileScripts(ScriptEngine engine, String... scripts) throws ScriptException
    {
        Compilable compilable = (Compilable)engine;
        ArrayList<CompiledScript> list = new ArrayList<>();
        for(String script : scripts)
            list.add(compilable.compile(script));
        return list;
    }

    public static void execute(ScriptEngine engine, List<CompiledScript> libs, String script) throws ScriptException
    {
        engine.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
        for(CompiledScript lib : libs)
            lib.eval();
        engine.eval(script);
    }

    public static void main(String[] args) throws ScriptException
    {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");
        List<CompiledScript> libs = compileScripts(engine, "var x = 1", "Object.prototype.test = function(arg){print(arg)}");

        // Prints 'hello'
        execute(engine, libs, "x.test('hello')");

        // Defines y
        execute(engine, libs, "var y = 2");

        // Checks that executions have a clean (non-polluted) context
        // Throws ReferenceError: "y" is not defined in <eval> at line number 1
        execute(engine, libs, "print(y)");
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-10
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    • 2012-03-29
    相关资源
    最近更新 更多