脚本引擎
简介
脚本引擎就是一个计算机编程语言的解释器
JAVA脚本引擎是从JDK6.0之后添加的新功能。
脚本引擎介绍:
- 使得Java应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在Java平台上调用各种脚本语言的目的。
- Java脚本API是连通Java平台和脚本语言的桥梁。
- 可以把一些复杂异变的业务逻辑交给脚本语言处理,这又大大提高了开发效率
使用场景
在日常的Java项目中,我们免不了会遇到这样的需求:
- 动态地获取并运行自定义脚本文件,以实现特定的功能
- 对数据流执行用户自定义的数据有效性、公式计算、数据处理ETL(如数据截取、拼接)等不同业务逻辑
- 对用户输入的代码或脚本文件进行测试、运行,确保其语法正确、功能正常
- 处理需要灵活配置且不断变更的动态业务规则
- 代码的热更新、热修复
诸如此类的需求若采用硬编码实现,则迭代成本相当高,每次改动都需要进行开发、测试、部署。同时业务规则的频繁变更会导致代码的开发和维护成本大大提高
js语言简介
js属于的是解释性语言。
支持动态类型,弱类型,在程序运行的时候才进行编译,效率不较低。
不像编译性语言,源代码不能直接翻译成机器语言,先翻译成中间代码,再由解释器对中间代码进行解释运行。
程序不需要编译,程序运行时才翻译成机器语言,每执行一次都要翻译一次。
一般,
编译性语言的运行效率比解释性语言更高
;但是不能一概而论,部分解释性语言的解释器通过在运行时动态优化代码,甚至能使解释性语言的性能超过编译性语言;
简单的实现过程
查找脚本引擎
通过脚本名称获取:
ScriptEngineManager:为ScriptEngine提供实例化机制。
方法:getEngineByName(String shortName);//查找并创建指定名称的脚本引擎
1
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
通过文件扩展名获取:
ScriptEngine:是一个接口,该接口提供了基本的脚本功能(包括执行脚本,设置和获取值的方法)。
1
2
3
4
5
6
7
8ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
方法:
get(String key);//获取指定key所对应的值,这里的key看做变量名称,值看做变量名所对应的值。
eval(String script);//执行指定的脚本代码
eval(Reader reader);//执行指定的脚本文件通过MIME类型获取:
1
ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");
语言绑定
脚本语言支持API使用语言绑定对象实现Java语言编写的程序与脚本语言间的数据传递。语言绑定对象实际上就是一个简单的哈希表,用来存放和获取需要共享的数据,其定义的接口为javax.script.Bindings,继承自java.util.Map接口。一个脚本引擎在执行过程中可能会使用多个语言绑定对象,不同语言绑定对象的作用域不同。ScriptEngine类提供out和get方法对脚本引擎中特定作用域的默认语言绑定对象进行操作。
使用默认的语言绑定对象:
1 | public void useDefaultBinding() throws ScriptException { |
亦可以自定义语言绑定对象(如语言绑定对象中包含程序自己独有的数据等情形……):
1 | public void useCustomBinding() throws ScriptException { |
脚本引擎执行脚本方法
一、
- 执行脚本方法,首先肯定是要在脚本中定义方法,然后执行。
- 执行时需要将engine转换为Invcable,然后调用其中的invokeFun
Invocable:由ScriptEngines实现的可选接口,其方法允许调用先前已执行的脚本中的程序(方法)。
- 调用Object invokeFunction(String name, Object… args);执行指定方法。
name为方法名称,args为方法参数,返回是一个Object对象
1 | import javax.script.Invocable; |
二、
- 执行脚本文件,首先我们要指定文件的路径。
- 通过这个路径构建一个Reader对象
- 调用eval(Reader reader);//执行指定的脚本文件
1 | import java.io.File; |
脚本执行的上下文
脚本引擎通过执行过程中的上下文对象获取与脚本执行相关的信息,同时允许程序员通过此对象配置脚本引擎的行为。其上下文对象来自javax.script.ScriptContext接口,类似于J2EE中javax.servlet.ServletContext接口,该接口主要包含3类信息:
输入输出
默认情况下,脚本输入输出都是在标准控制台中,可以通过setReader和setWriter方法对输出流进行重定向,可以通过setErrorWriter方法进行错误输出重定向。
1 | //例:将输出重定向到文件 |
自定义属性
上下文中通过setAttribute和getAttribute方法获取和设置属性,类似于ServletContext中设置和获取属性操作。与ServletContext中不同的是,ScriptContext中的属性是有作用域之分的,ScriptContext按不同的顺序在不同的作用域中进行属性查找(类似于JSP中EL表达式属性的作用域)。通过ScriptContext的getScopes可以得到其中所有可用的作用域,其中预定义了两个作用域:常量ScriptContext.ENGINE_SCOPE(当前的脚本引擎)和ScriptContext.GLOBAL_SCOPE(从同一引擎工厂中创建的所有脚本引擎对象)。
1 | public void scriptContextAttribute() { |
语言绑定对象
语言绑定对象位于ScriptContext中,同样也有作用域之分,范围越小,优先级越高。执行如下代码,输出的name值为Bob。
1 | public void scriptContextBindings() throws ScriptException { |
也可以通过ScriptContext获取语言绑定对象:
1 | public void useScriptContextValues() throws ScriptException { |
前面说到语言绑定对象存在于上下文环境中,故context中保存的自定义属性其实也是保存于语言绑定对象中的,如2中的语言绑定。
1 | public void attributeInBindings() throws ScriptException { |
脚本编译
脚本语言一般均是解释执行的,相对于编译执行的语言,效率较低一些。当脚本语言需要多次重复执行时,可以先对煎熬本进行编译,避免重复解析,提高效率(注:脚本编译需要脚本引擎支持,实现javax.script.Compilable接口)。JavaSE中自带的JavaScript引擎是支持对脚本进行编译的,编译的脚本用javax.script.CompiledScript来表示。
1 | public class ScriptCompile extends JsScriptRunner { |
方法调用
Java虚拟机支持脚本的意义在于实现函数式的编程,即脚本中最重要的便是方法。一些脚本引擎允许使用者单独调用脚本中的某个方法,支持此操作的脚本引擎可以通过实现javax.script.Invocable接口,支持顶层方法或者某对象中成员方法的调用。使用方法调用时最好先检查脚本引擎是否实现了Invocable接口,JavaSE中的JavaScript引擎已实现了Invocable接口。
在Java中调用脚本中的顶层方法
1
2
3
4
5
6
7public void invokeFunction() throws ScriptException, NoSuchMethodException {
ScriptEngine engine = getJavaScriptEngine();
String scriptText = "function greet(name) { println('Hello, ' + name); } ";
engine.eval(scriptText);
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("greet", "Alex");
}调用脚本中某对象的成员方法
1
2
3
4
5
6
7
8
9public void invokeMethod() throws ScriptException, NoSuchMethodException {
ScriptEngine engine = getJavaScriptEngine();
String scriptText = "var obj = { getGreeting : function(name) { return 'Hello, ' + name; } }; ";
engine.eval(scriptText);
Invocable invocable = (Invocable) engine;
Object scope = engine.get("obj");
Object result = invocable.invokeMethod(scope, "getGreeting", "Alex"); //第一个参数为方法所属对象
System.out.println(result);
}指定脚本中的方法为Java接口的实现
Greet是Java实现的接口,包含一个方法getGreeting,通过Invocable.getInterface()方法指定脚本中的方法为Java接口的实现。
1
2
3
4
5
6
7
8public void useInterface() throws ScriptException {
ScriptEngine engine = getJavaScriptEngine();
String scriptText = "function getGreeting(name) { return 'Hello, ' + name; } ";
engine.eval(scriptText);
Invocable invocable = (Invocable) engine;
Greet greet = invocable.getInterface(Greet.class);
System.out.println(greet.getGreeting("Alex"));
}