我正在使用Nashorn javascript引擎来评估在java应用程序中编写的所有服务器端javascript代码。为了提高性能,我在启动时使用spring初始化JsEngine并评估&缓存所有核心工具,如Mustache和一些 常见的JS工具。然后每次屏幕渲染时,这个预先评估的JsEngine将用于评估特定于页面的JavaScript代码。 它在某个时候工作正常,意味着它按预期呈现页面但在我持续点击相同的URL时开始抛出异常
我无法找到问题的根本原因。
@Component
public class JsEngine {
private ScriptEngine scriptEngine;
@PostConstruct
public void init() throws ScriptException, IOException{
scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
this.cacheAllCoreEngines();
for(String key: defaultEngineSource.keySet()){
scriptEngine.eval(defaultEngineSource.get(key));
}
}
private void cacheAllCoreEngines()throws IOException{
//read all core files such as mustache, etc.
defaultEngineSource.put("mustache", FileUtil.readFileFromDisk("<actual path..>/mustache.js"));
}
public Object eval(String source) throws ScriptException{
.... code to handle exceptions
return scriptEngine.eval (source);
}
}
JsEngine如下所示,
public class AppRendererImpl implements AppRenderer {
@Autowired
JsEngine jsEngine;
public String render(){
....
.... //Read source from disk or cache
jsEngine.eval(source);....
}
}
几个渲染周期后的异常,
例外:
java.lang.IllegalArgumentException:目标和过滤器类型不匹配:(ScriptObject)对象,(对象)对象
at java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:115)
at java.lang.invoke.MethodHandles.filterArgument(MethodHandles.java:2416)
at java.lang.invoke.MethodHandles.filterArguments(MethodHandles.java:2403)
at jdk.nashorn.internal.lookup.MethodHandleFactory $ StandardMethodHandleFunctionality.filterArguments(MethodHandleFactory.java:277)
at jdk.nashorn.internal.runtime.WithObject.filter(WithObject.java:270)
在jdk.nashorn.internal.runtime.WithObject.fixExpressionCallSite(WithObject.java:249)
在jdk.nashorn.internal.runtime.WithObject.lookup(WithObject.java:169)
在jdk.nashorn.internal.runtime.linker.NashornLinker.getGuardedInvocation(NashornLinker.java:96)
at jdk.internal.dynalink.support.CompositeTypeBasedGuardingDynamicLinker.getGuardedInvocation(CompositeTypeBasedGuardingDynamicLinker.java:176)
在jdk.internal.dynalink.support.CompositeGuardingDynamicLinker.getGuardedInvocation(CompositeGuardingDynamicLinker.java:124)
在jdk.internal.dynalink.support.LinkerServicesImpl.getGuardedInvocation(LinkerServicesImpl.java:144)
在jdk.internal.dynalink.DynamicLinker.relink(DynamicLinker.java:232)
在jdk.nashorn.internal.scripts.Script $ \ ^ eval _._ L6 $ _L8(:21)
at jdk.nashorn.internal.scripts.Script $ \ ^ eval _._ L6 $ _L40(:41)
在jdk.nashorn.internal.scripts.Script $ \ ^ eval_.runScript(:1)
在jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:498)
在jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:206)
在jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
在jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:546)
在jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:528)
在jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:524)
在jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:194)
在javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
在com.nube.portal.engines.js.JsEngine.eval(JsEngine.java:111)
在com.nube.portal.engines.html.tags.HtmlTagScript.eval(HtmlTagScript.java:66)
........
我添加了一些自定义代码,将所有全局对象复制到另一个地图。这是为了促进一些其他要求我必须访问所有全局对象作为&#34; nube。&#34;。我不知道这段代码是否会为频繁运行造成任何问题。请记住,我没有从Context中删除任何对象。
public void movePublicObjects(String prefix) throws NubeException{
Bindings b1 = scriptEngine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
Map<String, Object> nubeObjects = new HashMap<String, Object>();
for(Entry<String, Object> entry: b1.entrySet()){
if(!entry.getKey().equals("nube")){
nubeObjects.put(entry.getKey(), entry.getValue());
}
}
b1.put("nube", nubeObjects);
return;
}
当我将JsEngine定义为Prototype但性能不佳时,此代码非常有效。 你认为这是Nashorn的一个错误吗?
答案 0 :(得分:3)
IIRC,Spring @Component
将默认为单一范围,因此您的JsEngine
单个ScriptEngine
实例将在各个线程之间共享。
我在Nashorn开发人员邮件列表上找到了关于线程安全的a discussion,其中有这样说:
。 。 。 Nashorn的设计不是线程安全的。的确,如果你评估
new NashornScriptEngineFactory().getParameter("THREADING")
它将返回null,这意味着&#34;引擎实现不是线程安全的,并且不能用于在多个线程上同时执行脚本&#34; - 见http://docs.oracle.com/javase/7/docs/api/javax/script/ScriptEngineFactory.html#getParameter(java.lang.String)
Nashorn库内部本身是线程安全的。 。 。但是在单个引擎实例中执行的JavaScript程序不是线程安全的。
这是different from Rhino,如果您之前尝试过的话。使用Nashorn,您可能需要采取措施保护ScriptEngine
免受并发访问,这可能会解释您观察到的不可预测的行为。
考虑设置池,访问线程本地的引擎实例,或者将组件范围更改为 prototype 而不是singleton。
这是使用初始化脚本引擎的线程局部实例的潜在解决方案:
@Component
public class JsEngine {
private final ThreadLocal<ScriptEngine> threadEngines =
new ThreadLocal<ScriptEngine>() {
@Override
protected ScriptEngine initialValue() {
ScriptEngine engine =
new ScriptEngineManager().getEngineByName("nashorn");
for (String key: defaultEngineSource.keySet()) {
engine.eval(defaultEngineSource.get(key));
}
return engine;
}
};
@PostConstruct
public void init() throws ScriptException, IOException {
this.cacheAllCoreEngines();
// engine initialization moved to per-thread via ThreadLocal
}
private void cacheAllCoreEngines() throws IOException{
//read all core files such as mustache, etc.
defaultEngineSource.put("mustache", FileUtil.readFileFromDisk("<actual path..>/mustache.js"));
}
public Object eval(String source) throws ScriptException{
// .... code to handle exceptions
return threadEngines.get().eval(source);
}
}
对于使用组件的每个新线程,仍然会有初始化引擎(使用胡子等代码)的开销。但是,如果您的应用程序正在使用线程池,则应重用这些相同的线程,并且您不会像使用原型范围那样在每次调用时支付该成本。
答案 1 :(得分:1)
要么在持久性JS上下文中损坏某些内容,这会违反优化程序的假设,或者这是一个完全错误的错误。该堆栈跟踪显示您的代码调用了一个方法,并且脚本运行时内的代码路径设法到达一个点,该函数的优化版本即将应用于与其假设不匹配的参数(其参数类型过滤器)所以它失败了这个例外。无论您的JS代码如何,都不应该在一致的运行时中遵循该代码路径。