2.1.3 脚本执行上下文

与脚本引擎执行相关的另外一个重要的接口是javax.script.ScriptContext,其中包含脚本引擎执行过程中的相关上下文信息,可以与Java EE中servlet规范中的javax.servlet.ServletContext接口来进行类比。脚本引擎通过此上下文对象来获取与脚本执行相关的信息,也允许开发人员通过此对象来配置脚本引擎的行为。该上下文对象中主要包含以下3类信息。

1.输入与输出

首先介绍与脚本输入和输出相关的配置信息,其中包括脚本在执行中用来读取数据输入的java.io.Reader对象以及输出正确内容和出错信息的java.io.Writer对象。在默认情况下,脚本的输入输出都发生在标准控制台中。如果希望把脚本的输出写入到文件中,可以使用代码清单2-4中的代码。通过setWriter方法把脚本的输出重定向到一个文件中。通过ScriptContext的setReader和setErrorWriter方法可以分别设置脚本执行时的数据输入来源和产生错误时出错信息的输出目的。

代码清单2-4 把脚本运行时的输出写入到文件中的示例


public void scriptToFile()throws IOException, ScriptException{

ScriptEngine engine=getJavaScriptEngine();

ScriptContext context=engine.getContext();

context.setWriter(new FileWriter("output.txt"));

engine.eval("println('Hello World!');");

}


2.自定义属性

下面介绍执行上下文中包含的自定义属性。ScriptContext中也有与ServletContext中类似的获取和设置属性的方法,即setAttribute和getAttribute。所不同的是,ScriptContext中的属性是有作用域之分的。不同作用域的区别在于查找属性时的顺序不同。每个作用域都以一个对应的整数表示其查找顺序。该整数值越小,说明查找时的顺序越优先。优先级高的作用域中的属性会隐藏优先级低的作用域中的同名属性。因此,设置属性时需要显式地指定所在的作用域。在获取属性的时候,既可以选择在指定的作用域中查找,也可以选择根据作用域优先级自动进行查找。

不过脚本执行上下文实现中包含的作用域是固定的,开发人员不能随意定义自己的作用域。通过ScriptContext的getScopes方法可以得到所有可用的作用域列表。ScriptContext中预先定义了两个作用域:常量ScriptContext.ENGINE_SCOPE表示的作用域对应的是当前的脚本引擎,而ScriptContext.GLOBAL_SCOPE表示的作用域对应的是从同一引擎工厂中创建出来的所有脚本引擎对象。前者的优先级较高。代码清单2-5给出了作用域影响同名属性查找的一个示例。ENGINE_SCOPE中的属性“name”隐藏了GLOBAL_SCOPE中的同名属性。

代码清单2-5 作用域影响同名属性查找的示例


public void scriptContextAttribute(){

ScriptEngine engine=getJavaScriptEngine();

ScriptContext context=engine.getContext();

context.setAttribute("name","Alex",ScriptContext.GLOBAL_SCOPE);

context.setAttribute("name","Bob",ScriptContext.ENGINE_SCOPE);

context.getAttribute("name");//值为Bob

}


3.语言绑定对象

脚本执行上下文中的最后一类信息是语言绑定对象。语言绑定对象也是与作用域相对应的。同样的作用域优先级顺序对语言绑定对象也适用。这样的优先级顺序会对脚本执行时的变量解析产生影响。比如在代码清单2-6中,两个不同的语言绑定对象中都有名称为“name”的对象,而在脚本的执行过程中,作用域ENGINE_SCOPE的语言绑定对象的优先级较高,因此变量“name”的值是“Bob”。

代码清单2-6 语言绑定对象的优先级顺序的示例


public void scriptContextBindings()throws ScriptException{

ScriptEngine engine=getJavaScriptEngine();

ScriptContext context=engine.getContext();

Bindings bindings1=engine.createBindings();

bindings1.put("name","Alex");

context.setBindings(bindings1,ScriptContext.GLOBAL_SCOPE);

Bindings bindings2=engine.createBindings();

bindings2.put("name","Bob");

context.setBindings(bindings2,ScriptContext.ENGINE_SCOPE);

engine.eval("println(name);");

}


通过ScriptContext的setBindings方法设置的语言绑定对象会影响到ScriptEngine在执行脚本时的变量解析。ScriptEngine的put和get方法所操作的实际上就是ScriptContext中作用域为ENGINE_SCOPE的语言绑定对象。在代码清单2-7中,从ScriptContext中得到语言绑定对象之后,可以直接对这个对象进行操作。如果在ScriptEngine的eval方法中没有指明所使用的语言绑定对象,实际上起作用的是ScriptContext中作用域为ENGINE_SCOPE的语言绑定对象。

代码清单2-7 通过脚本执行上下文获取语言绑定对象的示例


public void useScriptContextValues()throws ScriptException{

ScriptEngine engine=getJavaScriptEngine();

ScriptContext context=engine.getContext();

Bindings bindings=context.getBindings(ScriptContext.ENGINE_SCOPE);

bindings.put("name","Alex");

engine.eval("println(name);");//输出Alex

}


上一小节介绍的自定义属性实际上也保存在语言绑定对象中。在代码清单2-8中,不直接操作语言绑定对象本身,而是通过ScriptContext的setAttribute来向语言绑定对象中添加数据。所添加的数据在脚本执行时也同样是可见的。

代码清单2-8 自定义属性保存在语言绑定对象中的示例


public void attributeInBindings()throws ScriptException{

ScriptEngine engine=getJavaScriptEngine();

ScriptContext context=engine.getContext();

context.setAttribute("name","Alex",ScriptContext.GLOBAL_SCOPE);

engine.eval("println(name);");//输出为Alex

}