Java 纳霍恩从globalscope到enginescope的原型变化

Java 纳霍恩从globalscope到enginescope的原型变化,java,nashorn,scriptengine,Java,Nashorn,Scriptengine,我正在尝试将一些库预加载到全局范围(比如chai.js)。这改变了一些对象的原型,我意识到这适用于引擎范围,但不适用于全局范围 最简单的例子: ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); Bindings globalBindings = engine.createBindings(); engine.eval("Object.prototype.test = function(arg){

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

最简单的例子:

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');");
ScriptEngine=newScriptEngineManager().getEngineByName(“nashorn”);
Bindings globalBindings=engine.createBindings();
eval(“Object.prototype.test=function(arg){print(arg);}”,全局索引);
//按预期工作,打印“hello”
engine.getContext().setBindings(globalBindings,ScriptContext.engine_范围);
eval(“var x={};x.test('hello');”;
//抛出类型错误:null不是第1行的函数
engine.getContext().setBindings(engine.createBindings(),ScriptContext.engine_作用域);
engine.getContext().setBindings(globalBindings,ScriptContext.GLOBAL_作用域);
eval(“var x={};x.test('hello');”;
是否有一种变通方法使其按预期工作,即将更改从全局范围正确传播到引擎范围?

为什么代码不工作 全局范围只能用于简单的变量映射。例如:

ScriptContext defCtx = engine.getContext();
defCtx.getBindings(ScriptContext.GLOBAL_SCOPE).put("foo", "hello");
Object
存在于引擎作用域中,因此全局作用域甚至不会搜索与其相关的任何映射(
Object.prototype.test

文件摘录:

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

解决
  • 继续使用引擎范围
  • 通过在Java命令行中指定
    -Dnashorn.args=--global per engine
    来使用
    --global per engine选项。然后,Nashorn将使用全局对象的单个实例进行所有脚本求值,而不管传递的ScriptContext如何
  • 使用完整的脚本上下文而不是绑定:
  • 如何运行几个脚本,每次都加载库,但以前的执行没有留下任何内容 每次需要库的新上下文时,只需创建它:

    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;
    }
    

    丹尼斯已经解释了这个问题,所以我不会重复他的话。不过,我会提出另一个解决办法。为了避免反复解析相同库的开销,可以编译它们。以下是一种方法:

    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)");
        }
    }
    
    import java.util.*;
    导入javax.script.*;
    公开课考试
    {
    公共静态列表编译器描述(ScriptEngine引擎、字符串…脚本)引发ScriptException
    {
    可编译=(可编译)引擎;
    ArrayList=新建ArrayList();
    for(字符串脚本:脚本)
    list.add(compileable.compile(script));
    退货清单;
    }
    公共静态void execute(ScriptEngine引擎、列表库、字符串脚本)引发ScriptException
    {
    engine.setBindings(engine.createBindings(),ScriptContext.engine_范围);
    用于(已编译脚本库:libs)
    lib.eval();
    engine.eval(脚本);
    }
    公共静态void main(字符串[]args)引发脚本异常
    {
    ScriptEngineManager管理器=新建ScriptEngineManager();
    ScriptEngine=manager.getEngineByName(“nashorn”);
    List libs=compileDescripts(引擎,“varx=1”,“Object.prototype.test=function(arg){print(arg)}”);
    //打印“你好”
    执行(引擎,libs,“x.test('hello')”);
    //定义y
    执行(引擎,libs,“变量y=2”);
    //检查执行是否具有干净(非污染)的上下文
    //抛出引用错误:“y”未在第1行中定义
    执行(引擎、libs、“打印(y)”);
    }
    }
    
    您使用的是哪个java版本和JVM实现?java脚本在java发布版本的过程中经历了一些变化,因为它是第一次添加的(我相信是java 7)。我使用的是java 11和Liberica jdk,也就是openjdk,我相信您知道-虽然我不知道这是否是您问题的原因。我知道。我在我的应用程序中附带了我自己的jdk,所以matterI难道不认为您希望每次都在加载库的情况下运行几个脚本,但之前的执行没有留下任何内容吗?例如,如果执行1在执行2开始时创建了变量x,那么您不希望x存在吗?感谢您给出了详尽的答案,尽管我希望有不同的解决方案,因为我希望避免重新评估。我将使用Oliver的解决方案来缓存编译后的代码,但我接受你的答案,因为它指出了实际的引用以及这不起作用的原因。
    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)");
        }
    }