对于每次执行,我计划创建一个由全局上下文支持的新上下文:
myContext.setBindings(engine.createBindings(),ScriptContext.ENGINE_SCOPE); engine.eval(myScript,myContext);
根据我读到的内容,对全局范围的任何修改(从脚本的角度来看)将仅限于我创建的新上下文.
这些脚本在评估时会公开一些对象(具有明确定义的名称和方法名称).我可以通过将引擎转换为Invocable来调用对象上的方法.但是,我如何知道函数运行的上下文?这甚至是一个问题,还是该功能的执行上下文是根据评估它的上下文设置的?
在多线程情况下,我可以期待什么行为,其中所有线程共享相同的脚本引擎实例,并且它们都尝试运行相同的脚本(公开全局对象).当我然后在对象上调用方法时,函数会在哪个上下文中运行?它将如何知道要使用的对象实例?
我期待看到一个invoke方法,我可以指定上下文,但似乎并非如此.有没有办法做到这一点,还是我完全错了?
我知道解决这个问题的一个简单方法是每次执行创建一个新的脚本引擎实例,但据我所知,我会失去优化(特别是在共享代码上).话虽如此,这里会预先编译帮助吗?
解决方法
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抛出一个未经检查的异常(Throwable包装在RuntimeException或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);
我用多个线程测试了这个,我能够确保状态更改仅限于属于单个线程的上下文.这是因为编译的表示在特定的上下文中执行,这意味着由它公开的任何事件的实例都限定在该上下文中.
这里的一个小缺点是,您可能不必要地重新评估不需要具有线程特定状态的对象的对象定义.要解决这个问题,请直接在引擎上对它们进行评估,这会将这些对象的绑定添加到引擎的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));