在网上找GraalVM 和 Graal-JS的资料,基本上只是详情特性的。。。很少有应用实战经验分享。
去年是想把自己的一个平台(在上面用js脚本动态开发接口,并对外提供服务)。之前是用jdk8自带的nashorn。
jdk11之后,移除了nashorn。。所以,想把它切换为graal-js,同时还可以支持es6。。。在与java的互通性方面,一下没有适配好。
最近终于成功了,所以分享一下!
var XContext = Java.type('org.noear.solon.core.XContext');
//java 里的接口:XFun.set(String,XFunHandler)XFun.set("log",function(map){ //...});
参考配置:https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/Context.Builder.html
//// 适配采用 ScriptEngine 的方案//var scriptEngineManager = new ScriptEngineManager();var _eng = scriptEngineManager.getEngineByName("graal.js");//增加配置,支持本地java对接(找了很多资料才找到)var bindings = _eng.getBindings(ScriptContext.ENGINE_SCOPE);//可以考虑这个,开启一切可开启的..//bindings.put("polyglot.js.allowAllAccess",true); bindings.put("polyglot.js.allowHostAccess", true);bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);/**如此之后,即可以支持nashorn里的少量特性了:Java.type()Java.from, Java.toJava.extend, Java.super*/
关于js的其它适配,请参考我的《nashorn的适配》(js 的其它适配解决类似)
此示例为嵌入式FaaS引擎 SolonJT 的一个执行器的适配
<dependency> <groupId>org.graalvm.js</groupId> <artifactId>js</artifactId> <version>19.2.0</version> <scope>runtime</scope></dependency><dependency> <groupId>org.graalvm.js</groupId> <artifactId>js-scriptengine</artifactId> <version>19.2.0</version> <scope>runtime</scope></dependency>
public class GraaljsJtExecutor implements IJtExecutor { private static final ThData<StringBuilder> _tlBuilder = new ThData(new StringBuilder(1024*5)); private static final String _lock =""; private static GraaljsJtExecutor _g; public static GraaljsJtExecutor singleton(){ if(_g == null){ synchronized (_lock){ if(_g == null){ _g = new GraaljsJtExecutor(); } } } return _g; } /** * Nashorn features available by default: * * Java.type, Java.typeName * Java.from, Java.to * Java.extend, Java.super * Java package globals: Packages, java, javafx, javax, com, org, edu * */ private final ScriptEngine _eng; private final Invocable _eng_call; private final Set<String> _loaded_names; private GraaljsJtExecutor(){ _loaded_names = Collections.synchronizedSet(new HashSet<>()); ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); _eng = scriptEngineManager.getEngineByName("graal.js"); //增加配置,支持本地java对接 Bindings bindings = _eng.getBindings(ScriptContext.ENGINE_SCOPE); bindings.put("polyglot.js.allowAllAccess",true); //bindings.put("polyglot.js.allowHostAccess", true); //bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true); _eng_call = (Invocable)_eng; XApp.global().shared().forEach((k, v)->{ sharedSet(k, v); }); XApp.global().onSharedAdd((k,v)->{ sharedSet(k, v); }); sharedSet("__JTEAPI", new __JTEAPI_CLZ()); try { StringBuilder sb = new StringBuilder(); sb.append("var __global={lib:{}};"); sb.append("Date.prototype.toJSON =function(){ return this.getTime()};"); sb.append("const XContext = Java.type('org.noear.solon.core.XContext');"); sb.append("const ONode = Java.type('org.noear.snack.ONode');"); sb.append("const Datetime = Java.type('org.noear.solonjt.utils.Datetime');"); sb.append("const Timecount = Java.type('org.noear.solonjt.utils.Timecount');"); sb.append("const Timespan = Java.type('org.noear.solonjt.utils.Timespan');"); sb.append("function modelAndView(tml,mod){return __JTEAPI.modelAndView(tml,mod);};"); sb.append("function require(path){__JTEAPI.require(path);return __global.lib[path]}"); //为JSON.stringify 增加java的对象解决 sb.append("function stringify_java(k,v){if(v){if(v.getTicks){return v.getTicks()}if(v.getTime){return v.getTime()}if(v.putAll){var obj={};v.forEach(function(k2,v2){obj[k2]=v2});return obj}if(v.addAll){var ary=[];v.forEach(function(v2){ary.push(v2)});return ary}}return v};"); sb.append("function API_RUN(api){var rst=api(XContext.current());if(rst){if(typeof(rst)=='object'){return JSON.stringify(rst,stringify_java)}else{return rst}}else{return null}};"); _eng.eval(sb.toString()); } catch (Exception ex) { ex.printStackTrace(); } } public void sharedSet(String name,Object val){ _eng.put(name, val); } // // IJtEngine 接口 // @Override public String language() { return "graaljs"; } @Override public boolean isLoaded(String name2) { return _loaded_names.contains(name2); } @Override public boolean preLoad(String name2, AFileModel file) throws Exception { if (isLoaded(name2) == false) { _loaded_names.add(name2); _eng.eval(compilerAsFun(name2, file)); } return true; } @Override public void del(String name) { String name2 = name.replace(".", "_").replace("*","_"); _loaded_names.remove(name2); _loaded_names.remove(name2 + "__lib"); } @Override public void delAll() { _loaded_names.clear(); } @Override public Object exec(String name, AFileModel file, XContext ctx, Map<String,Object> model, boolean outString) throws Exception { String name2 = name.replace(".","_").replace("*","_"); preLoad(name2, file); if(outString){ Object api = _eng.get("API_"+name2); Object tmp = _eng_call.invokeFunction("API_RUN",api); if (tmp == null) { return null; } else { return tmp.toString(); } }else{ return _eng_call.invokeFunction("API_"+name2, ctx); } } ////////////////////////////////////////////////////////////////// /** * 编译为函数代码 * */ public String compilerAsFun(String name, AFileModel file) { StringBuilder sb = _tlBuilder.get(); sb.setLength(0); sb.append("this.API_").append(name).append("=function(ctx){"); sb.append("\r\n\r\n"); sb.append(file.content); sb.append("\r\n\r\n};"); if (name.endsWith("__lib")) { sb.append("__global.lib['") .append(file.path) .append("']=") .append("new API_") .append(name) .append("();"); } return sb.toString(); }}