10

我通过 JSR 223 使用 Nashorn 来执行用户输入脚本的小片段:

public Invocable buildInvocable(String script) throws ScriptException {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName(ENGINE);
    engine.eval(functions);
    engine.eval(script);
    return (Invocable) engine;
}

不同的用户脚本调用定义在静态中央库中的 JavaScript 函数(保存在functions上面代码片段中的字符串中)。

每次我想获得一个Invocable我可以从我的 Java 调用的东西时,我都不得不不断地重新编译大型库代码。

有没有办法将以前编译的代码与新代码结合起来?

4

3 回答 3

18

将编译后的函数放入 Bindings 中,例如:

private static final String FUNCTIONS =
    "function() {" +
    "  return \"Hello\";" +
    "}";

public static void main(String... args) throws Exception {
    ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");

    // Compile common functions once
    CompiledScript compiled = ((Compilable) engine).compile(FUNCTIONS);
    Object sayHello = compiled.eval();

    // Load users' script each time
    SimpleBindings global = new SimpleBindings();
    global.put("sayHello", sayHello);
    String script = "sayHello()";
    System.out.println(engine.eval(script, global));
}
于 2014-11-27T11:55:57.980 回答
11

这是 JSR-223 的设计;eval(String)后面真的不能有代码缓存。好吧,理论上它可以,但它包含了开发人员想要的部分的大量猜测(并且正如所有猜测一样,它在某些时候肯定是错误的)。

你应该做的是评估你的Invocable一次,保留它并重复使用它。

这样做时,请注意 Nashorn 不提供线程安全(JavaScript 没有线程的概念,因此 Nashorn 故意不是线程安全的,以便在语言语义没有强制要求时不必支付同步成本)。出于这个原因,Invocable就底层脚本中的全局变量的状态而言,从多个线程中使用您创建的将是不安全的。(不与脚本的全局状态交互的同时运行的函数很好。)

如果您需要跨线程共享它并且函数依赖于全局状态,并且全局状态可以更改,那么您需要为此添加自己的脚手架(同步、资源池或其他任何当前流行的东西)以此目的)。

于 2014-03-31T09:53:57.023 回答
2

如果您需要预编译并使用各种参数调用 JavaSctipt 函数,您可以单独编译它们并在 Java 中组装执行流程。使用 Java8 中可用的 JavaScript 引擎 Nashorn,您可以:

private static final String FUNCTIONS =
  "function hello( arg ) {" +        //<-- passing java.lang.String from Java
    "  return 'Hello ' + arg;" +     //<-- returning string back
    "};" +
    "function sayTime( arg ) {" +   //<-- passing java.util.HashMap from Java
    "  return 'Java time ' + arg.get( 'time' );" +  //<-- returning string back
    "};" +
    "function () {" +                 //<-- this callable "function pointer" is being returned on [step1] below
    "  return { 'hello': hello, 'sayTime': sayTime };" +
    "};";

public static void main(String... args) throws Exception {
  ScriptEngine engine = new ScriptEngineManager().getEngineByName( "Nashorn" );

  CompiledScript compiled = ((Compilable) engine).compile(FUNCTIONS);
  ScriptObjectMirror lastFunction = (ScriptObjectMirror)compiled.eval();   // [step1]

  ScriptObjectMirror functionTable = (ScriptObjectMirror)lastFunction.call( null ); // this method retrieves function table
  String[] functionNames = functionTable.getOwnKeys( true );
  System.out.println( "Function names: " + Arrays.toString( functionNames ) );

  System.out.println( functionTable.callMember( "hello", "Robert" ) ); //<-- calling hello() with String as argiment

  Map map = new HashMap();
  map.put( "time", new Date().toString() ); //<-- preparing hashmap

  System.out.println( functionTable.callMember( "sayTime", map ) );  //<-- calling sayTime() with HashMap as argument
}

您可以在 JavaSctipt 中传递 Java 对象,请参见上面的 java.util.HashMap 示例。

输出是:

Function names: [hello, sayTime]
Hello Robert
Java time Fri Jan 12 12:23:15 EST 2018
于 2018-01-12T17:35:31.713 回答