我可以在 war 中捆绑更新版本的 Nashorn 吗?

Can I bundle a newer version of Nashorn in a war?

除非我遗漏了什么,否则这个版本的 Nashorn 似乎有一些错误:

$ jjs -v
nashorn 1.8.0_45

它在使用 3 位或更多位的多个积分作为 属性 键时感到窒息:

$ echo 'var c = JSON.parse("{\"123\": \"a\", \"456\": \"b\"}"); print(c["123"])' | jjs; echo
jjs> java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 7

2 位数字正常:

$ echo 'var c = JSON.parse("{\"12\": \"a\", \"45\": \"b\"}"); print(c["12"])' | jjs; echo
jjs> a

3位和2位给出不同的错误:

$ echo 'var c = JSON.parse("{\"123\": \"a\", \"45\": \"b\"}"); print(c["123"])' | jjs; echo
jjs> undefined

一个 3 位数字和一个字符串可以正常工作:

$ echo 'var c = JSON.parse("{\"123\": \"a\", \"foo\": \"b\"}"); print(c["123"])' | jjs; echo
jjs> a

使用这个版本一切正常:

$ jjs -v
nashorn 1.8.0_121

$ echo 'var c = JSON.parse("{\"123\": \"a\", \"456\": \"b\"}"); print(c["123"])' | jjs; echo
jjs> a

无论如何,以上片段只是演示我在我的网络应用程序中遇到的问题的一种方式。我的问题是 - 有没有办法在我的网络应用程序中捆绑这个较新版本的 nashorn,这样我就不需要在服务器上请求 java 升级?

原则上,我希望您能够从较新的 JRE 的 \lib\ext 文件夹中获取 nashorn.jar 并通过 maven-shade-plugin (depending on your build system) to relocate the packages 之类的东西 运行 它使用不同的名称(以免与您想要使用的底层 JVM 中的现有名称冲突)。您可能还想相应地更改正在制作的新 jar 中的 META-INF/services/javax.script.ScriptEngineFactory,并更改 NashornScriptEngineFactory.class 中的值,以便它具有不同的脚本引擎名称以供使用。然后你可以将它作为一个库添加到你的 war 文件中,然后使用 new ScriptEngineManager().getEngineByName("my-nashorn"); 或你给它的任何名称创建你的脚本引擎版本。

或者,您可以尝试自己从 its source code 编译 Nashorn,也许再次稍微修改它以赋予它您自己的引擎名称。我不确定这是否会更容易,因为我还没有尝试过任何一种方法。

这两种方法都假设:

  1. 您需要修复的错误是在 nashorn 代码本身中修复的错误,而不是在底层 JVM 中实际修复的错误。
  2. 这样做是在许可协议范围内。我还没有调查它,尽管它看起来 source code is GPL 2。但我不是开源专家。

我希望只更新服务器上 Java 的版本来修复已知错误(和 security patches!)会容易得多,但是如果你 need 到 运行 Java 的特定旧版错误版本上的某些特定代码的特定版本,您需要自己以某种方式提供该特定代码。

这是另一个不需要修改 nashorn jar 的解决方案:

  • 捆绑 nashorn.jar (*) 作为您 war
  • 中的资源文件
  • 使用 child-first/parent-last class 加载程序,例如 this one
  • 通过此 class 加载程序加载引擎

实现上述方法的示例 servlet,然后尝试使用 JRE 的 Nashorn 和捆绑的 Nashorn 评估您的脚本:

@WebServlet("/nashorn")
public class NashornDemoServlet extends HttpServlet {

    private static final ClassLoader CL;

    static {
        // In my case nashorn.jar is under WEB-INF/classes (resources root)
        URL nashornURL = NashornDemoServlet.class.getClassLoader().getResource("nashorn.jar");
        CL = new ParentLastURLClassLoader(Collections.singletonList(nashornURL));
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        String script = "var c = JSON.parse(\"{\\"123\\": \\"a\\", \\"456\\": \\"b\\"}\"); c[\"123\"]";

        ScriptEngine nashorn = new ScriptEngineManager(getClass().getClassLoader()).getEngineByName("nashorn");
        try {
            Object result = nashorn.eval(script);
            out.println("### JRE Nashorn result: " + result);
        } catch (Exception e) {
            out.println("### JRE Nashorn failed!");
            e.printStackTrace(out);
        }

        try {
            Class<?> clazz = CL.loadClass("jdk.nashorn.api.scripting.NashornScriptEngineFactory");
            Object factory = clazz.newInstance();
            ScriptEngine engine = (ScriptEngine) clazz.getMethod("getScriptEngine").invoke(factory);
            Object result = engine.eval(script);
            out.println("\n### Bundled Nashorn result: " + result);
        } catch (Exception e) {
            out.println("### Bundled Nashorn failed!");
            e.printStackTrace(out);
        }
    }
}

在 JRE 8u45 上使用 tomcat8 的结果:

### JRE Nashorn failed!
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 7
    at java.util.Arrays.rangeCheck(Arrays.java:120)
    at java.util.Arrays.fill(Arrays.java:2868)
    at jdk.nashorn.internal.runtime.BitVector.setRange(BitVector.java:273)
    ...
    at java.lang.Thread.run(Thread.java:745)

### Bundled Nashorn result: a

Web 应用程序项目树:

在此之前,我还尝试简单地将 nashorn.jar 捆绑在 WEB-INF/lib 下,而不使用自定义 class-loader 技巧(希望 servlet 容器的通常的子优先 class 加载器会足够了),但这没有用。我想这是因为 Tomcat 遵循 servlet 规范中的这条规则:

Servlet containers that are part of a Java EE product should not allow the application to override Java SE or Java EE platform classes, such as those in java.* and javax.* namespaces, that either Java SE or Java EE do not allow to be modified.

"",所以 jdk.* 似乎也属于这一类(无论如何,Tomcat 似乎确实排除了 Nashorn)。所以,是的,带上你自己的 ClassLoader

(*) 确保您可以合法地这样做。也许考虑使用来自 OpenJDK 构建的 jar,而不是从 Oracle Java 安装目录复制的。也许考虑 包括你自己,但提供将文件添加到你分发的 war 的说明(它只是一个 zip 文件)等