如何在 java/graalvm 中加载具有多个函数(每个文件同名)的 js 文件并通过文件名调用函数
How to load js files with multiple functions (of the same name per file) in java/graalvm and invoke functions by file name
我有一个服务器应用程序,它在启动时加载多个脚本文件(用于处理特定的数据集字段)。脚本应该被解析,脚本的“表达式”数据应该存储在映射中(按列名),以便以后可以从那里访问和执行它们。
有两种类型的脚本。 “简单”的只包含一个 process
函数,复杂的目前具有类似于下面示例的结构(可能有更多私有 functions/fields):
// public
function process(input) {
return _doSomething(input);
}
function selfTest() {
if (process("123") !== "123") throw "failed";
// ...
}
// private
var _allowedSymbols = ['H', 'L', 'M'];
function _doSomething(input) {
// _allowedSymbols is used here
}
process
和 selfTest
是服务器应用程序将使用的“public”函数。 selfTest
会在文件loaded/evaluated后执行一次,process
会在需要的时候对传入的数据执行
我从旧的 JSR 223 方式开始:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal.js");
engine.eval("function process(input) { return input.toUpperCase(); }");
// engine.eval("function process(input) { return input + '123'; }");
Invocable inv = (Invocable) engine;
Object result = inv.invokeFunction("process", "peter");
这种方法存在函数数据存储在 javascript 引擎实例中的问题,因此我的映射中不能有多个“处理”方法。
我可以继续这种方式,并根据列名动态生成函数和全局变量的名称前缀...但那是...“丑陋”...
我已经尝试了 graalvm-context-way(在 SO 和 Oleg 的帮助下,):
var ctx = Context.newBuilder("js").allowAllAccess(false).build();
var src = Source.newBuilder("js", "(function u(input) { return input.toUpperCase(); })", "test.js").build();
var script = ctx.eval(src);
var result = script.execute("peter");
这适用于“简单”功能。但是对于复杂的脚本,上面的函数表达式方式不起作用。
编辑(解决方案):
稍微修改了 Oleg 的答案,这似乎可以完成工作...
var jsCode = """
(function() {
function process(input) { return input; }
function selfTest() { if (process("123") !== "123") throw "failed"; return true; }
return { process, selfTest };
})();
""";
var ctx2 = Context.newBuilder("js").allowAllAccess(false).build();
Source source = Source.newBuilder("js", jsCode, "test.js").build();
var script = ctx2.eval(source);
var fnProcess = script.getMember("process");
var result = fnProcess.execute("123");
var fnSelfTest = script.getMember("selfTest");
var result2 = fnSelfTest.execute();
要么函数是在顶级命名空间中声明的,那么名称冲突就是一个问题,要么它们在它们的自定义范围内,然后你必须有一些方法来访问和调用它们。
当您评估这样的来源时:
(function u(input) { return input.toUpperCase(); })
评估的结果是脚本中的最后一个表达式。
你可以想到这行:
var result = ctx.eval("js", "(function u(input) { return input.toUpperCase(); })");
大约就像在 JS 中一样:
result = (function u(input) { return input.toUpperCase(); })
因此,您可以 运行 它具有:
result("HelloWorld");
这意味着您可以 return 使用辅助对象的多个函数:
// public
function process(input) {
return _doSomething(input);
}
function selfTest() {
if (process("123") !== "123") throw "failed";
// ...
}
// private
var _allowedSymbols = ['H', 'L', 'M'];
function _doSomething(input) {
// _allowedSymbols is used here
}
returnMe = {process, selfTest};
这正是您在 JavaScript 中所做的(我认为)。
然后你可以用
得到过程函数
var returnedObject = ctx.eval(src);
var processFunction = returnedObject.getMember("process");
var result = processFunction.execute("peter");
并类似地访问selfTest
函数。
您需要为此修改 JS 源代码可能不太理想,但我认为这是必要的。
我有一个服务器应用程序,它在启动时加载多个脚本文件(用于处理特定的数据集字段)。脚本应该被解析,脚本的“表达式”数据应该存储在映射中(按列名),以便以后可以从那里访问和执行它们。
有两种类型的脚本。 “简单”的只包含一个 process
函数,复杂的目前具有类似于下面示例的结构(可能有更多私有 functions/fields):
// public
function process(input) {
return _doSomething(input);
}
function selfTest() {
if (process("123") !== "123") throw "failed";
// ...
}
// private
var _allowedSymbols = ['H', 'L', 'M'];
function _doSomething(input) {
// _allowedSymbols is used here
}
process
和 selfTest
是服务器应用程序将使用的“public”函数。 selfTest
会在文件loaded/evaluated后执行一次,process
会在需要的时候对传入的数据执行
我从旧的 JSR 223 方式开始:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal.js");
engine.eval("function process(input) { return input.toUpperCase(); }");
// engine.eval("function process(input) { return input + '123'; }");
Invocable inv = (Invocable) engine;
Object result = inv.invokeFunction("process", "peter");
这种方法存在函数数据存储在 javascript 引擎实例中的问题,因此我的映射中不能有多个“处理”方法。
我可以继续这种方式,并根据列名动态生成函数和全局变量的名称前缀...但那是...“丑陋”...
我已经尝试了 graalvm-context-way(在 SO 和 Oleg 的帮助下,
var ctx = Context.newBuilder("js").allowAllAccess(false).build();
var src = Source.newBuilder("js", "(function u(input) { return input.toUpperCase(); })", "test.js").build();
var script = ctx.eval(src);
var result = script.execute("peter");
这适用于“简单”功能。但是对于复杂的脚本,上面的函数表达式方式不起作用。
编辑(解决方案):
稍微修改了 Oleg 的答案,这似乎可以完成工作...
var jsCode = """
(function() {
function process(input) { return input; }
function selfTest() { if (process("123") !== "123") throw "failed"; return true; }
return { process, selfTest };
})();
""";
var ctx2 = Context.newBuilder("js").allowAllAccess(false).build();
Source source = Source.newBuilder("js", jsCode, "test.js").build();
var script = ctx2.eval(source);
var fnProcess = script.getMember("process");
var result = fnProcess.execute("123");
var fnSelfTest = script.getMember("selfTest");
var result2 = fnSelfTest.execute();
要么函数是在顶级命名空间中声明的,那么名称冲突就是一个问题,要么它们在它们的自定义范围内,然后你必须有一些方法来访问和调用它们。
当您评估这样的来源时:
(function u(input) { return input.toUpperCase(); })
评估的结果是脚本中的最后一个表达式。 你可以想到这行:
var result = ctx.eval("js", "(function u(input) { return input.toUpperCase(); })");
大约就像在 JS 中一样:
result = (function u(input) { return input.toUpperCase(); })
因此,您可以 运行 它具有:
result("HelloWorld");
这意味着您可以 return 使用辅助对象的多个函数:
// public
function process(input) {
return _doSomething(input);
}
function selfTest() {
if (process("123") !== "123") throw "failed";
// ...
}
// private
var _allowedSymbols = ['H', 'L', 'M'];
function _doSomething(input) {
// _allowedSymbols is used here
}
returnMe = {process, selfTest};
这正是您在 JavaScript 中所做的(我认为)。
然后你可以用
得到过程函数var returnedObject = ctx.eval(src);
var processFunction = returnedObject.getMember("process");
var result = processFunction.execute("peter");
并类似地访问selfTest
函数。
您需要为此修改 JS 源代码可能不太理想,但我认为这是必要的。