прототип меняется от глобалоскопа до мотороскопа в Нашорне - PullRequest
3 голосов
/ 31 марта 2020

Я пытаюсь предварительно загрузить некоторые библиотеки в глобальную область (например, 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');");

Есть ли обходной путь, чтобы заставить его работать как положено, то есть изменения правильно распространяются из глобальной области видимости в движок?

Ответы [ 2 ]

1 голос
/ 05 апреля 2020

Денис уже объяснил проблему, поэтому я не буду повторять то, что он сказал. Я, однако, дам другое решение. Чтобы избежать лишних разборов одних и тех же библиотек снова и снова, вы можете скомпилировать их. Вот способ сделать это:

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)");
    }
}
1 голос
/ 04 апреля 2020

Почему ваш код не работает

Глобальная область может использоваться только для простого отображения переменных. Например:

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. Используйте --global-per-engine option, указав -Dnashorn.args=--global-per-engine в вашей Java командной строке. Затем Nashorn будет использовать один экземпляр глобального объекта для всех оценок сценариев независимо от переданного ScriptContext.
  3. Использовать полноценный ScriptContext вместо привязок:
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;
}
...