Java ScriptEngine Local против глобальной области действия и отложенной декларации - PullRequest
0 голосов
/ 30 ноября 2018

У меня есть сценарий, когда я обрабатываю предоставленные пользователем шаблоны, которые содержат части JavaScript.Я использую Java ScriptEngine для оценки этих кусочков JavaScript.У меня есть различные вспомогательные функции, которые я хочу оценивать только один раз (представьте, что эти вспомогательные функции загружают что-то вроде Underscore.js).

После загрузки я перебираю строки данных и обрабатываю шаблон длякаждая строка - эти шаблоны могут использовать любую из ранее загруженных вспомогательных функций.Я пытаюсь обрабатывать строки параллельно, поскольку ScriptEngine.eval() в Java в этом сценарии довольно медленный.

Любой, кто смотрит на это по упрощенному SSCCE, будет знать, что я получаю ожидаемый результат, который я показал, так какЯ изменяю глобальный объект не потокобезопасным способом.В моем примере sJsGlobal это глобальный JavaScript.Глобальные функции нуждаются в доступе к различным битам, специфичным для строк, которые я помещаю в obj.

Для sJsGlobal для успешного eval() необходимо знать о obj, иначе произойдет сбой.В моем SSCCE каждый поток обновляет один и тот же глобальный obj перед вызовом функции.Я ищу способ для каждой глобальной функции видеть локальный obj.Это ситуация Catch-22, мне нужно определить переменную для глобального сценария, чтобы он знал, что он существует, но я хочу, чтобы он имел локальное значение.

import java.util.*;
import java.util.concurrent.*;
import javax.script.*;

public class SSCCE {
  public static void main(String[] args) throws Exception {
    StringBuilder sJsGlobal = new StringBuilder();
    sJsGlobal.append("var obj = {};");
    sJsGlobal.append("function get_row() { return \"Row is \" + obj.row }"); 

    ScriptEngine jsGlobal = new ScriptEngineManager().getEngineByName("JavaScript");
    jsGlobal.eval(sJsGlobal.toString());

    Bindings gB = jsGlobal.getBindings(ScriptContext.ENGINE_SCOPE);

    ExecutorService es = Executors.newCachedThreadPool();
    ArrayList<Future<String>> af = new ArrayList<Future<String>>();

    for (int i = 0; i < 10; i++) {
      final int fi = i;
      af.add(es.submit(() -> {
        StringBuilder sJsLocal = new StringBuilder();
        sJsLocal.append("obj.row = " + fi + ";");
        sJsLocal.append("var x = get_row();");

        ScriptEngine jsLocal = new ScriptEngineManager().getEngineByName("JavaScript");
        ScriptContext sc = new SimpleScriptContext();
        Bindings lB = jsLocal.createBindings();
        lB.putAll(gB);
        sc.setBindings(lB, ScriptContext.ENGINE_SCOPE);
        jsLocal.setContext(sc);

        jsLocal.eval(sJsLocal.toString());
        return (String)jsLocal.get("x");
      }));
    }

    for (Future<String> f : af) {
      System.out.println("Returned -> " + f.get());
    }
    es.shutdown();
  }
}

Это приводит к следующему выводу, гдестроки перезаписываются, так как они не являются потокобезопасными из-за моего использования этой глобальной переменной obj.Даже с отдельными ScriptEngineManager и ScriptEngine.Мне нужен способ настройки привязок, чтобы они не мешали другим экземплярам.

Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 8
Returned -> Row is 5
Returned -> Row is 6
Returned -> Row is 7
Returned -> Row is 8
Returned -> Row is 9

Я мог бы передавать специфичные для строки данные в каждую функцию как переменные, но это становится очень грязным, так как мой пример не 'Я не могу судить о количестве данных, которые мне нужно обменять.Функции также вызываются пользовательскими скриптами, что делает это очень трудным.Я действительно хочу найти способ переопределить obj в каждом потоке, прежде чем оценивать sJsLocal.

Это все работает нормально, если я обрабатываю каждый ряд один за другим без многопоточности, но я пытаюсь ускорить этонемного.

1 Ответ

0 голосов
/ 30 ноября 2018

После долгих испытаний, ошибок и усилий и прочтения множества похожих вопросов без ответов я пришел к следующему, которое, похоже, работает и задокументировано здесь для всех, кто сталкивался с этой проблемой.При этом используется общая ScriptEngine с отдельным ScriptContext на поток для обеспечения изоляции.

import java.util.*;
import java.util.concurrent.*;
import javax.script.*;

public class SSCCE {
  public static void main(String[] args) throws Exception {
    ScriptEngine js = new ScriptEngineManager().getEngineByName("JavaScript");

    StringBuilder sJsGlobal = new StringBuilder();
    sJsGlobal.append("var obj = {};");
    sJsGlobal.append("function get_row() { return \"Row is \" + obj.row; }"); 
    CompiledScript jsLib = ((Compilable)js).compile(sJsGlobal.toString());

    ExecutorService es = Executors.newCachedThreadPool();
    ArrayList<Future<String>> af = new ArrayList<Future<String>>();

    for (int i = 0; i < 10; i++) {
      final int fi = i;
      af.add(es.submit(() -> {
        StringBuilder sJsLocal = new StringBuilder();
        sJsLocal.append("obj.row = " + fi + ";");
        sJsLocal.append("var x = get_row();");

        ScriptContext scLocal = new SimpleScriptContext();
        scLocal.setBindings(js.createBindings(), ScriptContext.ENGINE_SCOPE);
        jsLib.eval(scLocal);
        js.eval(sJsLocal.toString(), scLocal);
        return (String)scLocal.getAttribute("x");
      }));
    }

    for (Future<String> f : af) {
      System.out.println("Returned -> " + f.get());
    }
    es.shutdown();
  }
}

В результате при запуске:

Returned -> Row is 0
Returned -> Row is 1
Returned -> Row is 2
Returned -> Row is 3
Returned -> Row is 4
Returned -> Row is 5
Returned -> Row is 6
Returned -> Row is 7
Returned -> Row is 8
Returned -> Row is 9
...