Java 11 中的 Nashorn 在评估命名函数时的行为与 Java 8 不同

2022-09-03 16:17:26

我有一个Java应用程序,它允许用户通过定义JavaScript函数在运行时操作某些对象。我们目前正在Java 8中使用Nashorn来做这件事,但我们希望迁移到Java 11。一旦我们上了Java 11,我们将能够在GraalVM中提供此功能,但现在我们需要保持Java 8 ->Nashorn脚本的Java 11升级的兼容性。

在Java 11中,当我们评估函数时,Nashorn的行为似乎有所不同,这取决于该函数是否被命名,而Java 8中的情况并非如此。下面是在 Java 11 中使用 JJS 的示例:

$ jjs -v
nashorn 11.0.6
Warning: The jjs tool is planned to be removed from a future JDK release
jjs> function foo() {}
jjs> function () {}
function () {}

请注意,第一个函数定义不返回任何内容。在 Java 8 中,即使函数被命名为,它也会返回该函数:

$ jjs -v
nashorn 1.8.0_252
jjs> function foo() {}
function foo() {}

我们目前调用这些脚本的方式是:

CompiledScript compiled = scriptEngine.compile(userProvidedScript);
Object evaled = compiled.eval(bindings);
scriptEngine.invokeMethod(evaled, "call", evaled, ... input parameters ...)

好奇是否有人知道这种情况的根本原因以及任何好的解决方法?我需要支持以及出于反向兼容的原因。由于这是在我们的Java应用程序中完成的,因此我们可能会以某种方式包装用户提供的脚本,或者尝试从绑定中获取脚本(这似乎容易出错,因为可以定义多个脚本,并且Java 8行为将是调用最后定义的脚本)。function(...)function foo(...)


答案 1

可能是由于 Nashorn 的匿名函数语句自定义功能(这些实际上称为“函数声明”)意外地应用于实际命名的函数声明的问题引起的。由于Nashorn文档中没有对此进行描述,我认为他们不希望这种行为并摆脱了它。

溶液:

转换源代码,以便作为其最终操作,它生成由上一个命名的函数声明定义的函数对象。

考虑一下我的测试,看看这将在OpenJDK 8和11中工作:

nashorn @ 1.8.0_302:

jjs> function bar() { foo(); }; function foo() { bar(); }     
function foo() { bar(); }

jjs> function bar() { foo(); }; function foo() { bar(); }; foo
function foo() { bar(); }

nashorn @ 11.0.6:

jjs> function bar() { foo(); }; function foo() { bar(); }

jjs> function bar() { foo(); }; function foo() { bar(); }; foo
function foo() { bar(); }

要弄清楚要使用什么名称,您应该能够解析JS并使用jdk.nashorn.api.tree处理其AST。

您的树访问者/函数名称累加器可能如下所示:

private static class FuncDeclarationVisitor extends SimpleTreeVisitorES5_1<Void, Void> {
    public String lastFunctionName = null;

    @Override
    public Void visitFunctionDeclaration(FunctionDeclarationTree node, Void param) {
        // Anonymous function declaration ==> null
        IdentifierTree functionName = node.getName();
        lastFunctionName = functionName != null ? functionName.getName() : null;
        return super.visitFunctionDeclaration(node, param);
    }
}

您可以像这样调用它:

CompilationUnitTree parsedTree;     // Use Parser's parse method

FuncDeclarationVisitor visitor = new FuncDeclarationVisitor();
parsedTree.accept(visitor);

return visitor.lastFunctionName;     // Null if the last function declaration was anonymous

但是我不认为有任何方法可以修改AST,然后将其发送到的编译器。您可能必须向源文本本身添加一些内容NashornScriptEngine

此外,您可能还希望访问者检测其他类型的非函数声明节点,这样您就不会意外地转换脚本并删除可能产生的其他表达式(非函数)。


答案 2