【问题标题】:Can I bundle a newer version of Nashorn in a war?我可以在战争中捆绑更新版本的 Nashorn 吗?
【发布时间】:2017-08-21 19:29:05
【问题描述】:

除非我在这里遗漏了什么,否则这个版本的 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

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

【问题讨论】:

  • 没有。但是您可以添加一个发布说明,说明 需要 Java JDK 1.8.0 Build 121 或更高版本
  • 您是否询问在您的应用程序中重新分发 Java 版本的法律要求?或者关于将嵌入式 Java Nashorn 运行时与您的应用程序捆绑并将其用于某些操作的技术要求?还是两者兼而有之?
  • 后者 - 我想知道是否可以在不升级 java 版本的情况下让用户使用更新版本的 nashorn?

标签: java nashorn


【解决方案1】:

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

  • nashorn.jar (*) 捆绑为您的战争中的资源文件
  • 使用子优先/父最后的类加载器,例如this one
  • 通过此类加载器加载引擎

实现上述方法的示例 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 上使用 tomcat 8 的结果:

### 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 下,而无需自定义类加载器技巧(希望通常的 servlet 容器的子类加载器就足够了),但这不起作用。我想这是因为 Tomcat 遵循 servlet 规范中的这条规则:

作为 Java EE 产品一部分的 Servlet 容器不应允许应用程序覆盖 Java SE 或 Java EE 平台类,例如 java.*javax.* 命名空间中的那些,Java SE 或 Java EE 都不允许允许修改。

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

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

【讨论】:

    【解决方案2】:

    原则上,我希望您应该能够从较新的 JRE 的 \lib\ext 文件夹中获取 nashorn.jar,并通过类似 maven-shade-plugin(取决于您的构建系统)到 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!)会变得容易得多,但如果您需要运行一些特定的在特定的 Java 旧错误版本上的某些特定代码的版本,您需要以某种方式自己提供该特定代码。

    【讨论】:

    • 这是一个很好的答案,彼得。感谢您的贡献!不幸的是,Hugues 的回答似乎更容易一些,所以我先尝试了一下,效果很好,所以我要给他赏金。
    • @andersonbd1:哦,我同意,我自己也赞成他的回答。我希望我能想到。 :)
    • 这个也不错 (+1),在某些情况下甚至会更好...有时 SO 鼓励“one-true-answer”的政策很烦人:-/ 使用 maven shade 插件也许有一个resource transformer 可以处理META-INF/services/javax.script.ScriptEngineFactory。重定位(通过 shade 插件或手动重构+编译)的优点是,当涉及到修改后的 Nashorn 时,您可以区分堆栈跟踪。
    • 我将为这种方法(重命名包)添加另一个好处是您不会遇到使用像 ScriptObjectMirror 这样的 nashorn 对象的困难。当 2 个不同的类具有相同的包时,您会遇到无法从一个类转换到另一个类的奇怪异常。
    • 哈,是的,java.lang.ClassCastException: com.acme.Car cannot be cast to com.acme.Car ...我在最近的另一个答案中提到了这一点,我提出了一个儿童优先类加载器技巧,但这次它躲过了我:]
    猜你喜欢
    • 2012-06-26
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 1970-01-01
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 2015-10-14
    相关资源
    最近更新 更多