我正在使用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)作用域那样在每次调用时支付该费用。

09-30 20:53