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

2022-09-03 16:01:50

我正在使用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: target and filter types not match: (ScriptObject)Object, (Object)Object
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)
at jdk.nashorn.internal.runtime.WithObject.fixExpressionCallSite(WithObject.java:249)
at jdk.nashorn.internal.runtime.WithObject.lookup(WithObject.java:169)
atjdk.nashorn.internal.runtime.linker.NashornLinker.getGuardedInvocation(NashornLinker.java:96)
at jdk.internal.dynalink.support.CompositeTypeBasedGuardingDynamicLinker.getGuardedInvocation(CompositeTypeBasedGuardingDynamicLinker.java:176)
at jdk.internal.dynalink.support.CompositeGuardingDynamicLinker.getGuardedInvocation(CompositeGuardingDynamicLinker.java:124)
at jdk.internal.dynalink.support.LinkerServicesImpl.getGuardedInvocation(LinkerServicesImpl.java:144)
at jdk.internal.dynalink.DynamicLinker.relink(DynamicLinker.java:232)
at jdk.nashorn.internal.scripts.Script$\^eval_._L6$_L8(:21)
at jdk.nashorn.internal.scripts.Script$\^eval_._L6$_L40(:41)
at jdk.nashorn.internal.scripts.Script$\^eval_.runScript(:1)
at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:498)
at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:206)
at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:546)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:528)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:524)
at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:194)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
at com.nube.portal.engines.js.JsEngine.eval(JsEngine.java:111)
at com.nube.portal.engines.html.tags.HtmlTagScript.eval(HtmlTagScript.java:66)
........

我添加了一些自定义代码,将所有全局对象复制到另一个映射。这是为了方便我必须以“nube”的形式访问所有全局对象的其他要求。我不知道此代码是否会为频繁运行带来任何问题。请记住,我不会从上下文中删除任何对象。

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定义为原型时,这段代码可以完美地工作,但性能并不好。你认为这是纳斯霍恩的一个错误吗?


答案 1

IIRC,Spring 将默认为单例作用域,因此您的 单个实例将在线程之间共享。@ComponentJsEngineScriptEngine

我在Nashorn开发者邮件列表中发现了一个关于线程安全的讨论,上面写着:

. . .Nashorn在设计上不是线程安全的。事实上,如果你评估

new NashornScriptEngineFactory().getParameter("THREADING")

它将返回 null,这意味着“引擎实现不是线程安全的,不能用于在多个线程上同时执行脚本” - 请参阅 http://docs.oracle.com/javase/7/docs/api/javax/script/ScriptEngineFactory.html#getParameter(java.lang.String)

Nashorn 库内部本身就是线程安全的。 。 。但是在单个引擎实例中执行的 JavaScript 程序不是线程安全的。

这与Rhino不同,如果你以前尝试过的话。使用 Nashorn,您可能需要采取措施来保护自己免受并发访问,这也许可以解释您观察到的不可预测的行为。ScriptEngine

请考虑设置池、访问线程本地的引擎实例,或将组件范围更改为原型而不是单例。


下面是使用初始化脚本引擎的线程本地实例的潜在解决方案:

@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);
    }
}

对于使用组件的每个新线程,初始化引擎(使用胡子等代码)仍然会有开销。但是,如果您的应用程序使用线程池,那么应该重用这些相同的线程,并且您不会像使用原型范围那样在每次调用时支付该成本。


答案 2

要么你在持久性JS上下文中损坏了某些东西,然后违反了优化器的假设,要么这是一个彻头彻尾的错误。该堆栈跟踪显示您的代码去调用一个方法,并且脚本运行时中的代码路径设法到达一个点,其中函数的优化版本将应用于与其假设不匹配的参数(其参数类型筛选器),因此它失败并出现此异常。该代码路径永远不应该在一致的运行时中遵循,无论您的JS代码如何。


推荐