我正在使用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);....
}
}
几个渲染周期后发生异常,
我添加了一些自定义代码,以将所有全局对象复制到另一张 map 。这是为了满足一些其他要求,即我必须将所有全局对象作为“nube”进行访问。我不知道这段代码是否会造成频繁运行的问题。请记住,我没有从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中的错误?
最佳答案
IIRC,Spring @Component
将默认为单例作用域,因此您的JsEngine
的单个ScriptEngine
实例将在线程之间共享。
我在Nashorn开发人员邮件列表中发现有关线程安全的a discussion,上面写着:
如果您以前尝试过,则为different from Rhino。使用Nashorn,您可能需要采取措施来保护ScriptEngine
免受并发访问,这可能可以解释您观察到的不可预测的行为。
考虑设置一个池,访问线程本地的引擎实例,或将组件范围更改为原型(prototype)而不是单例。
这是使用初始化脚本引擎的线程本地实例的潜在解决方案:
@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);
}
}
对于使用组件的每个新线程,初始化引擎(带有 mustache 等代码)的开销仍然很大。但是,如果您的应用程序正在使用线程池,则应该重用那些相同的线程,并且不会像使用原型(prototype)作用域那样在每次调用时支付该费用。