来自Nashorn的IllegalArgumentException异常 - 这是Java 8中的错误吗?

时间:2014-12-07 20:48:21

标签: java spring java-8 nashorn

我正在使用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的一个错误吗?

2 个答案:

答案 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代码如何,都不应该在一致的运行时中遵循该代码路径。