【问题标题】:Nashorn binding issueNashorn 绑定问题
【发布时间】:2016-08-09 23:37:44
【问题描述】:

我有一个类似下面的 java 类

    public class StaticBean {

        public static String returnString(int num){
            String json = "[{\"name\" : \"John Doe\", \"age\" : 30}]";
            return json;
        }

    }

在下面的测试中,我有两个引擎实例。

在执行时,我发现无法将绑定实例复制到另一个实例上并将其用于在同一引擎或不同引擎中执行。即使我复制,如果我使用相同的引擎/绑定,结果与我得到的结果不匹配。

@Test
    public void testParsingStringObjects() {

        Bindings b = engine.createBindings();
        b.put("ndb", getBindingObject(StaticBean.class, engine));

        engine.setBindings(b, ScriptContext.ENGINE_SCOPE);


        String source = "print('Definition in engine instance 1');" 

                + "var SysU = {};\n"

                + "SysU.returnObject = function returnObjectJS(){\n" 

                + "var string = ndb.returnString(1);\n"

                + "return JSON.parse(string);\n" + "}\n"

                + "SysU.returnString = function returnStringJS(){\n" 

                + "var string = ndb.returnString(1);\n"

                + "print('String returned by the java function SysU.returnString() '+string);\n" 

                + "return string;\n" + "};\n"

                + "print('====================Using the same engine instance for execution====================');\n"

                + "(function (){" + "var json = {};\n"

                + "print(\"String Returned in Caller SysU.returnString(): \"+SysU.returnString());\n"

                + "print(\"Object Returned in Caller SysU.returnObject(): \"+SysU.returnObject());\n"

                + "print(\"**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): \"+JSON.stringify(SysU.returnObject()));\n"

                + "print('Adding the object in another ( json.ext = SysU.returnObject();) ...');\n" 

                + "json.ext = SysU.returnObject();\n"

                + "print(\"Added JSON object which is stringified to display JSON.stringify(json): \"+JSON.stringify(json));\n" + "})();";


        try {
            engine.eval(source);

            Bindings oldEngineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
            Bindings localBindings = engine2.getBindings(ScriptContext.ENGINE_SCOPE);
            Bindings newBindings = engine.createBindings();

            oldEngineBindings.put("fileName","oldEngine");
            localBindings.put("fileName","localEngine");


            newBindings.putAll(oldEngineBindings);
            newBindings.putAll(localBindings);

            oldEngineBindings.putAll(localBindings);

            ScriptContext ctxt = new SimpleScriptContext();
            ctxt.setBindings(oldEngineBindings, ScriptContext.ENGINE_SCOPE);

            engine.setContext(ctxt);

            engine.eval(""
                    + "print('====================Using the same engine with original binding ====================');\n"
                    + "(function (){" + "var json = {};\n"

                    + "print(\"String Returned in Caller SysU.returnString(): \"+SysU.returnString());\n"

                    + "print(\"Object Returned in Caller SysU.returnObject(): \"+SysU.returnObject());\n"

                    + "print(\"**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): \"+JSON.stringify(SysU.returnObject()));\n"

                    + "print('Adding the object in another ( json.ext = SysU.returnObject();) ...');\n" 

                    + "json.ext = SysU.returnObject();\n"

                    + "print(\"Added JSON object which is stringified to display JSON.stringify(json): \"+JSON.stringify(json));\n" + "})();");

            ctxt.setBindings(newBindings, ScriptContext.ENGINE_SCOPE);

            engine.setContext(ctxt);

            engine.eval(""
                    + "print('====================Using the same engine with copied new binding ====================');\n"
                    + "(function (){" + "var json = {};\n"

                    + "print(\"String Returned in Caller SysU.returnString(): \"+SysU.returnString());\n"

                    + "print(\"Object Returned in Caller SysU.returnObject(): \"+SysU.returnObject());\n"

                    + "print(\"**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): \"+JSON.stringify(SysU.returnObject()));\n"

                    + "print('Adding the object in another ( json.ext = SysU.returnObject();) ...');\n" 

                    + "json.ext = SysU.returnObject();\n"

                    + "print(\"Added JSON object which is stringified to display JSON.stringify(json): \"+JSON.stringify(json));\n" + "})();",newBindings);

            ctxt.setBindings(oldEngineBindings, ScriptContext.ENGINE_SCOPE);

            engine2.setContext(ctxt);

            engine2.eval(""
                    + "print('====================Using a different engine instance with original binding ====================');\n"
                    + "(function (){" + "var json = {};\n"

                    + "print(\"String Returned in Caller SysU.returnString(): \"+SysU.returnString());\n"

                    + "print(\"Object Returned in Caller SysU.returnObject(): \"+SysU.returnObject());\n"

                    + "print(\"**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): \"+JSON.stringify(SysU.returnObject()));\n"

                    + "print('Adding the object in another ( json.ext = SysU.returnObject();) ...');\n" 

                    + "json.ext = SysU.returnObject();\n"

                    + "print(\"Added JSON object which is stringified to display JSON.stringify(json): \"+JSON.stringify(json));\n" + "})();");

        } catch (ScriptException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


    }

这是一种可接受的行为吗?还是错误?我应该能够复制绑定并在同一引擎或不同引擎实例的不同范围内使用它们并产生相同的结果。

我正在 Java8u101 上进行测试

运行测试时的结果。 当绑定或引擎实例更改时,ReturnObject() 函数似乎失败了。

Definition in engine instance 1
====================Using the same engine instance for execution====================
String returned by the java function SysU.returnString() [{"name" : "John Doe", "age" : 30}]
String Returned in Caller SysU.returnString(): [{"name" : "John Doe", "age" : 30}]
Object Returned in Caller SysU.returnObject(): [object Object]
**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): [{"name":"John Doe","age":30}]
Adding the object in another ( json.ext = SysU.returnObject();) ...
Added JSON object which is stringified to display JSON.stringify(json): {"ext":[{"name":"John Doe","age":30}]}
====================Using the same engine with original binding ====================
String returned by the java function SysU.returnString() [{"name" : "John Doe", "age" : 30}]
String Returned in Caller SysU.returnString(): [{"name" : "John Doe", "age" : 30}]
Object Returned in Caller SysU.returnObject(): [object Object]
**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): [{"name":"John Doe","age":30}]
Adding the object in another ( json.ext = SysU.returnObject();) ...
Added JSON object which is stringified to display JSON.stringify(json): {"ext":[{"name":"John Doe","age":30}]}
====================Using the same engine with copied new binding ====================
String returned by the java function SysU.returnString() [{"name" : "John Doe", "age" : 30}]
String Returned in Caller SysU.returnString(): [{"name" : "John Doe", "age" : 30}]
Object Returned in Caller SysU.returnObject(): [object Object]
**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): undefined
Adding the object in another ( json.ext = SysU.returnObject();) ...
Added JSON object which is stringified to display JSON.stringify(json): {}
====================Using a different engine instance with original binding ====================
String returned by the java function SysU.returnString() [{"name" : "John Doe", "age" : 30}]
String Returned in Caller SysU.returnString(): [{"name" : "John Doe", "age" : 30}]
Object Returned in Caller SysU.returnObject(): [object Object]
**Stringified Object Returned in Caller JSON.stringify(SysU.returnObject()): undefined
Adding the object in another ( json.ext = SysU.returnObject();) ...
Added JSON object which is stringified to display JSON.stringify(json): {}

编辑:-

找到这个帖子https://bugs.openjdk.java.net/browse/JDK-8067642。这提到了关于外部对象是 ScriptObjectMirror 实例的一些事情。我使用 typeof 运算符来显示在失败和成功的情况下返回的对象类型,并且两次都是 ScriptObjectMirror 但如果我在上下文中使用原始绑定对象,则 stringify 会按预期工作。

编辑 2:-

添加了一个非常简单的测试来演示上述内容。有点像上面的 TLDR :)。执行下面的代码展示了绑定对象上的 putAll() 并没有像我们预期的那样工作。

@Test
    public void testParsingObjects() throws ScriptException {

        String source = "var Func = {};\n"
                + "Func.getJavaScriptObject = function(){"
                + "var jsString = '{\"foo\":\"bar\"}';\n"
                + "return JSON.parse(jsString);"
                + "};";


        String executor = "(function(){ "
                + "var obj = Func.getJavaScriptObject();"
                + "print(JSON.stringify(obj));"
                            + " })();";

        System.out.println("Executing source...");
        engine.eval(source);
        System.out.println("\nUsing the same binding instance and engine\n");
        engine.eval(executor);

        Bindings originalBinding = engine.getBindings(ScriptContext.ENGINE_SCOPE);


        Bindings copiedBinding = engine.createBindings();

        copiedBinding.putAll(originalBinding);

        System.out.println("\nUsing the copied binding instance and engine\n");
        engine.eval(executor,copiedBinding);


    }

执行结果。

Executing source...

Using the same binding instance and engine

{"foo":"bar"}

Using the copied binding instance and engine

undefined

【问题讨论】:

    标签: javascript java nashorn


    【解决方案1】:

    这是我在不同线程使用的 ScriptContext 实例之间使用共享编译的 JavaScript 代码的代码。这里的主要好处是只编译一次代码,尽管我也受益于不需要多次从 REST API 流式传输代码。为简洁起见,我没有包含 REST 部分。

    import javax.script.CompiledScript; 
    import javax.script.ScriptContext; 
    import javax.script.ScriptEngine; 
    import javax.script.ScriptEngineManager; 
    import javax.script.ScriptException; 
    import javax.script.SimpleScriptContext; 
    import java.util.concurrent.Callable; 
    import java.util.concurrent.ExecutorService; 
    import java.util.concurrent.Executors; 
    import java.util.concurrent.Future; 
    import java.util.concurrent.ExecutionException; 
    import java.util.ArrayList; 
    import java.util.List; 
    
    public class ContextDemo {
        static CompiledScript codeLib; 
        static ScriptEngine engine; 
        static ScriptContext context; 
        static List <Future<String>> taskResults; 
        static ExecutorService executor; 
        static List <Callable<String>> tasks = new ArrayList<Callable<String>> (); 
    
        public static void main(String[] args) {
            try {
                // Initialize workers and execute
                run(4); 
            } catch(InterruptedException | ExecutionException | ScriptException e) {
                System.out.println(e.getMessage()); 
            }
        }
    
        static void run(int workers) throws InterruptedException, ExecutionException, ScriptException {
            // Initialize engine and initial context
            engine = new ScriptEngineManager().getEngineByName("nashorn"); 
            context = new SimpleScriptContext(); 
            engine.setContext(context); 
            // Compile a JavaScript object with a function
            codeLib = ((javax.script.Compilable)engine).compile("var lib = { func1: function(n, s) { return 'thread number ' + n + ': ' + s; } };"); 
            // Create executor with specified number of workers
            executor = Executors.newFixedThreadPool((int)workers); 
            for (int i = 0; i < workers; i++) {
                tasks.add(workerLambda(i)); 
            }
            // Invoke worker pool
            taskResults = executor.invokeAll(tasks); 
            // Iterate futures list and report results
            for (int i = 0; i < workers; i++) {
                Future < String > f = taskResults.get(i); 
                if (f.isDone()) {
                    System.out.println(f.get()); 
                } else {
                    System.out.println("Thread " + i + " not done"); 
                }
            }
            // Shutdown the executor
            executor.shutdown(); 
        }
    
        static Callable <String> workerLambda(int n) {
            int workerNum = n; 
            // Thread-specific script context initialization
            SimpleScriptContext threadContext = new SimpleScriptContext(); 
            threadContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); 
            try {
                // Inject compiled code library into thread-specific ScriptContext
                codeLib.eval(threadContext); 
            } catch (ScriptException e1) {
                System.out.println(e1.getMessage()); 
            }
            // Return the lambda
            return () ->  {
                // Call the injected object method and return the result
                return (String)engine.eval("lib.func1(" + workerNum + ", 'Hello!');", threadContext); 
            }; 
        }
    }
    

    这个输出:

    thread number 0: Hello!
    thread number 1: Hello!
    thread number 2: Hello!
    thread number 3: Hello!
    

    【讨论】:

    • 这是一个有趣的实现,尽管我怀疑它是否可以在我的场景中工作。我无法将此作为问题的答案进行检查,因为它不能解决问题。一旦我在我的代码中测试了这一点,我将回到这个线程并让你发布。
    【解决方案2】:

    我希望我可以为此提供一个可行的解决方案,但我怀疑它可能是 Nashorn 错误。作为证据,我将此链接提交给一个旧的 JDK 错误:

    https://bugs.openjdk.java.net/browse/JDK-8023363

    在链接中提供了Test.java(下)作为证据,证明这两个问题(Map 中缺少关键存在以及在 .putAll() 进入新绑定后无法执行函数)都是“不是问题”。除了我测试了相同的代码并得到了不同的结果:

    1. 即使在 eval 之后,原始 Bindings 似乎也只包含键“nashorn.global”
    2. 尝试使用新绑定执行“func(x)”(在 putAll() 之后)会引发引用错误

    Test.java 如下:

    import javax.script.*;
    import java.util.Map;
    
    public class Test {
       public static void main(String[] args) throws Exception {
           ScriptEngineManager m = new ScriptEngineManager();
           ScriptEngine scriptEngine = m.getEngineByName("nashorn");
    
           Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
           scriptEngine.eval("function func(x) { print('I am func ' + x); }");
    
           // print stuff exposed in engineBindings
           for (Map.Entry<?,?> entry : engineBindings.entrySet()) {
               System.out.println(entry.getKey());
               System.out.println(entry.getValue());
           }
    
           Bindings localBindings = scriptEngine.createBindings();
           // copy all exposed from other bindings
           localBindings.putAll(engineBindings);
           // put additional variable
           localBindings.put("event", new Object());
           scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE);
    
           scriptEngine.eval("func(event)");
       }
    }
    

    【讨论】:

    • 原始绑定将具有函数 func(x)。它可能被包裹在另一个对象( ScriptObjectMirror )中。引用不是这里的问题,而是当您执行 binding.putAll() 时目标实例中缺少某些信息。我可以在复制的绑定中引用函数或变量。
    • 有趣。我很想看到那个代码。尽管如此,取自 bugs.openjdk.java.net/browse/JDK-8023363 的 Test.java 示例代码不会产生与创建时相同的结果,因此从那时起行为似乎发生了变化。 FWIW,我解决了在 ScriptContexts 甚至 ScriptEngine 实例之间共享代码和对象的问题,而无需使用 Bindings 或 putAll()。相反,我创建 CompiledScript 对象,然后使用 context.eval(myObj); 将它们注入我需要的任何新 ScriptContext 对象中。稍后我会发布示例代码。
    • 我不确定我是否关注你。据我所知,您不能在 ScriptContext 实现上调用 eval() 。但如果你能告诉我你是如何做到的,我将不胜感激。现在,我运行一个线程本地引擎实例。
    • 错字我试图修复太晚了。真正的行是编译 myObj.eval(context) 将 myObj 注入到 ScriptContext 中。对不起。 FWIW,我从 REST API 获取代码,编译成 CompiledScript 对象,然后将它们注入到同一个 ScriptEngine 上的线程特定的 ScriptContext 实例中。效果很好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-12
    • 1970-01-01
    • 2016-03-19
    相关资源
    最近更新 更多