使用Nashorn的Java脚本(JSR 223)&预编译

时间:2014-03-31 01:17:50

标签: java scripting jsr223 nashorn

我通过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字符串中)。

每当我想获得一个我可以从Java调用的Invocable时,我就不得不重新编译大型库代码。

有没有办法用新代码加入以前编译过的代码?

3 个答案:

答案 0 :(得分:16)

将已编译的函数放入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));
}

答案 1 :(得分:10)

这是JSR-223的设计; eval(String)背后无法真正拥有代码缓存。好吧,理论上它可以,但它体现了开发人员想要的很多猜测(并且所有猜测,它在某些时候肯定是错误的)。

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

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

如果你需要在线程之间共享它们的功能取决于全局状态,全局状态可以改变,那么你需要添加自己的脚手架为此目的(同步,或资源池,或其他目前为此目的的方式)。

答案 2 :(得分:1)

如果您需要预先编译并使用各种参数调用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