GraalJS及平台JS脚本能力建设
GraalJS替换Nashorn
Oracle宣布弃用Nashorn Javascript引擎,最终将从未来所有的JDK中删除。
Nashorn最初是在JDK 8中引入的,用于取代Rhino脚本引擎。发布时,Nashorn是ECMAScript-262 5.1的完整实现,增强了Java和JavaScript的兼容性,并增加了新的ECMAScript 6(ES6)特性。借助Nashorn,开发人员可以从JavaScript调用Java代码,也可以从Java代码调用JavaScript函数。Nashorn实现javax.script API,弃用Nashorn不会影响javax.script API。
Oracle实验室高级研究总监Thomas Wuerthinger表示,GraalVM是一个不错的替代方案,与Nashorn相比,它的性能更好,与ECMAScript的兼容性也更好。
Javax.script API
javax.script 脚本API由定义Java脚本引擎的接口和类组成,提供在Java应用程序中的使用脚本的框架。
javax.script包的功能主要包括
脚本执行:脚本是用作脚本引擎执行的程序源的字符流。 使用ScriptEngine的eval方法和Invocable接口的方法执行脚本。
绑定:允许Java对象作为命名变量绑定给脚本程序。 见:Bindings和ScriptContext类。
编译:允许重复存储和执行脚本引擎前端生成的中间代码。 这有利于多次执行相同脚本的应用程序以提高效率,因为引擎的前端只需要每个脚本执行一次,而不是每次执行脚本一次。 请注意,此功能是可选的,脚本引擎可能会选择不实现它。需要使用instanceof来检查Compilable接口的可用性。
调用:允许重用脚本引擎前端生成的中间代码。 编译允许重新执行由中间代码表示的整个脚本,而调用功能允许重新执行脚本中的各个过程/方法。 与编译的情况一样,并非所有脚本引擎都需要提供此功能。须检查Invocable的可用性。
脚本引擎发现: 引擎发现机制基于ServiceLoader类中描述的服务提供者加载工具。 ScriptEngineManager包括getEngineFactories方法,以获取所有ScriptEngineFactory实例。 ScriptEngineFactory具有查询脚本引擎属性的方法。
使用Graal js引擎执行js代码
<dependency> <groupId>org.graalvm.js</groupId> <artifactId>js</artifactId> <version>${graal.version}</version> </dependency> <dependency> <groupId>org.graalvm.js</groupId> <artifactId>js-scriptengine</artifactId> <version>${graal.version}</version> </dependency> |
import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class JsEngineHelloWorld { public static void main(String[] args) throws ScriptException { ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal.js"); engine.eval("print('Hello World!');"); } } |
JS和Java对象交互:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); bindings.put("polyglot.js.allowHostAccess", true); bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true); bindings.put("javaObj", new Object()); engine.eval("(javaObj instanceof Java.type('java.lang.Object'));"); // it will not work without allowHostAccess and allowHostClassLookup |
平台JS脚本能力建设
当平台需要面对复杂、多变的业务时,基于成熟的领域模型和脚本能力能提供灵活的扩展性,可以考虑从以下层面建立平台脚本能力:
执行引擎层:Graal JS、 Nashorn
平台JS引擎:基于底层引擎平台封装。
- 引擎启动和关闭管理。
- 多线程(并发): 如使用线程池并发执行脚本。
- 监控及运营环境管理:如资源管控和限制。
- 基础能力注入(提供JS API): 提供机制插入基础能力至JS环境
创建Java对象代理并注入
JS服务层:
- 基础JS能力: 基于平台产品能力提供JS能力。基于JS引擎的对象绑定功能,提供公共及Java开发的JS对象能力。
- 统一的JS执行API: 如统一的API接口
- 应用层JS脚本管理。
- 基础能力注入(提供JS API): 提供机制插入基础能力至JS环境。
//加载静态资源 Resource[] otherResources = (new PathMatchingResourcePatternResolver()).getResources("classpath*:*.js"); List<String> jsResources = (List)Arrays.stream(otherResources).map((x) -> { try { return IOUtils.toString(x.getInputStream(), StandardCharsets.UTF_8); } catch (IOException var2) { throw new IllegalArgumentException(x.getFilename() + "load failed "); } }).collect(Collectors.toList()); String staticSource = String.join("\n", jsResources); //初始化静态脚本 context.eval(staticSource); |
//提供特定接口用于提供增加能力对象(pluginClass) Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(pluginClass); enhancer.setCallback(interceptor); T insertObject = enhancer.create(); private static final TypeLiteral<Map<String, Object>> STRING_MAP = new TypeLiteral<Map<String, Object>>() {}; map = (Map)this.context.eval(Source.newBuilder("js", "this", "internal-script").internal(internal).buildLiteral()).as(STRING_MAP); //特定标识插入JS引用对象 map.put("XXXX", insertObject) |
应用JS:
- 应用JS脚本开发
应用场景:
基于业务模型数据经常需要开发特定数据发分析, 对于大数据场景可以使用大数据技术来进行数据挖掘和分析, 对于短周期或小批量的数据,基于灵活多变规则的实时分析可以考虑使用JS脚本来解决规则多变的问题