Сценарии Java с Nashorn (JSR 223) и прекомпиляция

Я использую Nashorn через JSR 223 для выполнения небольших фрагментов введенного пользователем сценария:

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, мне постоянно приходится перекомпилировать код большой библиотеки.

Есть ли способ объединить ранее скомпилированный кусок кода с новым кодом?

3 ответа

Решение

Это дизайн JSR-223; eval(String) на самом деле не может иметь кеш кода. Ну, теоретически это могло бы быть, но в нем было бы много спекуляций со стороны того, что хочет разработчик (и, как и все спекуляции, иногда оно было бы ошибочным).

Что вы должны сделать, это оценить свои Invocable один раз, держите его и используйте его несколько раз.

При этом обратите внимание, что Nashorn не обеспечивает безопасность потоков (в JavaScript нет концепции потоков, поэтому Nashorn намеренно не безопасен для потоков, чтобы не платить расходы на синхронизацию, если они не предписаны семантикой языка). По этой причине вы создали Invocable не будет безопасно использовать из нескольких потоков в отношении состояния глобальных переменных в базовом сценарии. (Параллельно работающие функции, которые не взаимодействуют с глобальным состоянием скрипта, это нормально.)

Если вам нужно разделить его между потоками, а функции зависят от глобального состояния, и глобальное состояние может измениться, то для этого вам нужно будет добавить свои собственные леса (синхронизация, или пул ресурсов, или что-то еще, что в настоящее время в моде). для этого).

Поместите скомпилированные функции в привязки, такие как:

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));
}

Если вам нужна предварительная компиляция и вызов функций JavaSctipt с различными аргументами, вы можете скомпилировать их отдельно и собрать поток выполнения в Java. С движком JavaScript Nashorn, доступным в Java8, вы можете сделать:

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
}

Вы можете передавать объекты Java внутри JavaSctipt, см. Пример с java.util.HashMap выше.

Выход:

Function names: [hello, sayTime]
Hello Robert
Java time Fri Jan 12 12:23:15 EST 2018
Другие вопросы по тегам