我可以在 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,也许再次稍微修改它以赋予它您自己的引擎名称。我不确定这是否会更容易,因为我还没有尝试过任何一种方法。
这两种方法都假设:
- 您需要修复的错误是在 nashorn 代码本身中修复的错误,而不是在底层 JVM 中实际修复的错误。
- 这样做是在许可协议范围内。我还没有调查它,尽管它看起来 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 文件)等
除非我遗漏了什么,否则这个版本的 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,也许再次稍微修改它以赋予它您自己的引擎名称。我不确定这是否会更容易,因为我还没有尝试过任何一种方法。
这两种方法都假设:
- 您需要修复的错误是在 nashorn 代码本身中修复的错误,而不是在底层 JVM 中实际修复的错误。
- 这样做是在许可协议范围内。我还没有调查它,尽管它看起来 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.*
andjavax.*
namespaces, that either Java SE or Java EE do not allow to be modified.
"如",所以 jdk.*
似乎也属于这一类(无论如何,Tomcat 似乎确实排除了 Nashorn)。所以,是的,带上你自己的 ClassLoader
(*) 确保您可以合法地这样做。也许考虑使用来自 OpenJDK 构建的 jar,而不是从 Oracle Java 安装目录复制的。也许考虑 不 包括你自己,但提供将文件添加到你分发的 war 的说明(它只是一个 zip 文件)等