javascript - 在 Nashorn 中的特定上下文中执行函数

标签 javascript java concurrency scope nashorn

我有一个自定义的 Nashorn 运行时,我设置了一些全局函数和对象——其中一些是无状态的,一些是有状态的。针对此运行时,我正在运行一些自定义脚本。

对于每次执行,我计划创建一个由全局上下文支持的新上下文:

myContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
engine.eval(myScript, myContext);

根据我阅读的内容,对全局范围的任何修改(从脚本的 Angular 来看)都将限于我创建的新上下文。

这些脚本在评估时会公开一些对象(具有明确定义的名称和方法名称)。我可以通过将 engine 转换为 Invocable 来调用对象的方法。但是我怎么知道函数运行的上下文呢?这甚至是一个问题,还是该函数的执行上下文是根据评估它的上下文设置的?

在所有线程共享同一个脚本引擎实例并且它们都尝试运行同一个脚本(公开一个全局对象)的多线程情况下,我可以期待什么行为?当我随后调用对象上的方法时,该函数将在哪个上下文中运行?它如何知道要使用对象的哪个实例?

我期待看到一个 invoke 方法,我可以在其中指定上下文,但事实似乎并非如此。有没有办法做到这一点,还是我的做法完全错误?

我知道解决这个问题的一个简单方法是在每次执行时创建一个新的脚本引擎实例,但据我所知,我会失去优化(尤其是在共享代码上)。话虽这么说,预编译在这里有帮助吗?

最佳答案

我想通了。我遇到的问题是 invokeFunction 会抛出 NoSuchMethodException,因为自定义脚本公开的函数在引擎默认范围的绑定(bind)中不存在:

ScriptContext context = new SimpleScriptContext();
context.setBindings(nashorn.createBindings(), ScriptContext.ENGINE_SCOPE);
engine.eval(customScriptSource, context);
((Invocable) engine).invokeFunction(name, args); //<- NoSuchMethodException thrown

所以我要做的就是通过名称从上下文中提取函数并像这样显式调用它:

JSObject function = (JSObject) context.getAttribute(name, ScriptContext.ENGINE_SCOPE);
function.call(null, args); //call to JSObject#isFunction omitted brevity 

这将调用存在于您新创建的上下文中的函数。您还可以通过这种方式调用对象的方法:

JSObject object = (JSObject) context.getAttribute(name, ScriptContext.ENGINE_SCOPE);
JSObject method = (JSObject) object.getMember(name);
method.call(object, args);

call 抛出未经检查的异常(包裹在 RuntimeException 中的 Throwable 或已用初始化的 NashornException JavaScript 堆栈框架信息),因此如果您想提供有用的反馈,您可能必须明确处理这些信息。

这样线程就不能跨过彼此,因为每个线程都有一个单独的上下文。我还能够在线程之间共享自定义运行时代码,并确保对自定义运行时公开的可变对象的状态更改由上下文隔离。

为此,我创建了一个 CompiledScript 实例,其中包含我的自定义运行时库的编译表示:

public class Runtime {

    private ScriptEngine engine;
    private CompiledScript compiledRuntime;

    public Runtime() {
        engine = new NashornScriptEngineFactory().getScriptEngine("-strict");
        String source = new Scanner(
            this.getClass().getClassLoader().getResourceAsStream("runtime/runtime.js")
        ).useDelimiter("\\Z").next();

        try {
            compiledRuntime = ((Compilable) engine).compile(source);
        } catch(ScriptException e) {
            ...
        }
    }

    ...
}

然后,当我需要执行脚本时,我会评估编译后的源代码,然后也针对该上下文评估脚本:

ScriptContext context = new SimpleScriptContext();
context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);

//Exception handling omitted for brevity

//Evaluate the compiled runtime in our new context
compiledRuntime.eval(context); 

//Evaluate the source in the same context
engine.eval(source, context);

//Call a function
JSObject jsObject = (JSObject) context.getAttribute(function, ScriptContext.ENGINE_SCOPE);
jsObject.call(null, args);

我用多个线程对此进行了测试,我能够确保状态更改仅限于属于各个线程的上下文。这是因为编译表示是在特定上下文中执行的,这意味着它公开的任何实例都在该上下文范围内。

这里的一个小缺点是您可能会不必要地重新评估不需要具有线程特定状态的对象的对象定义。为了解决这个问题,直接在引擎上评估它们,这会将这些对象的绑定(bind)添加到引擎的 ENGINE_SCOPE:

public Runtime() {
    ...
    String shared = new Scanner(
        this.getClass().getClassLoader().getResourceAsStream("runtime/shared.js")
    ).useDelimiter("\\Z").next();

    try {
        ...        
        nashorn.eval(shared);
        ...
    } catch(ScriptException e) {
        ...
    }
}

然后,您可以从引擎的 ENGINE_SCOPE 填充特定于线程的上下文:

context.getBindings(ScriptContext.ENGINE_SCOPE).putAll(engine.getBindings(ScriptContext.ENGINE_SCOPE));

您需要做的一件事是确保您公开的任何此类对象都已被卡住。否则,可以重新定义或向它们添加属性。

关于javascript - 在 Nashorn 中的特定上下文中执行函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33620318/

相关文章:

java - 我的代码线程不安全吗?

c# - 无法安全地锁定 ConcurrentDictionary 的值

java - Java : what's the point? 中的 Thread.interrupt()

Java 性能 : true vs. Boolean.TRUE

java.sql.SQLException : Column 'max_grade' not Found where sql query do not contain 'max_grade'

javascript - ng-show 和 ng-if 无法显示内容

javascript - Twitter-bootstrap 导航栏下拉菜单表现异常(不在移动设备上工作,不在桌面上显示)

java - 自定义 SharedPreferences 类

javascript - 搜索时jquery select2自定义javascript函数

javascript - Nodejs 异步库中的简单 map 函数调用不起作用