У меня есть программа Java 7, которая загружает тысячи объектов (компонентов), каждая со многими параметрами (хранится в Map
) и выполняет различные сценарии Rhino на этих объектах для вычисления других производных параметров, которые сохраняются назад в объекте Map
. Перед запуском каждого скрипта создается объект Scope
, поддерживаемый картой объекта, который используется в качестве области JavaScript в течение всего сценария.Захват глобальных переменных Nashorn
В качестве простого примера, следующий создает HashMap
с = 10 и В = 20, и выполняет сценарий c = a + b
, что приводит к c = 30.0
сохраненных назад в карте. Хотя сценарий похож на создание глобальной переменной c
, объект Scope
захватывает его и сохраняет его на карте; другой скрипт выполняется с другим объектом Scope
не увидит эту переменную:
public class Rhino {
public static void main(String[] args) throws ScriptException {
Context cx = Context.enter();
Scriptable root_scope = cx.initStandardObjects();
Map<String, Object> map = new HashMap<>();
map.put("a", 10);
map.put("b", 20);
Scope scope = new Scope(root_scope, map);
cx.evaluateString(scope, "c = a + b", "<expr>", 0, null);
System.out.println(map); // --> {b=20, c=30.0, a=10}
Context.exit();
}
static class Scope extends ScriptableObject {
private Map<String, Object> map;
public Scope(Scriptable parent, Map<String, Object> map) {
setParentScope(parent);
this.map = map;
}
@Override
public boolean has(String key, Scriptable start) {
return true;
}
@Override
public Object get(String key, Scriptable start) {
if (map.containsKey(key))
return map.get(key);
return Scriptable.NOT_FOUND;
}
@Override
public void put(String key, Scriptable start, Object value) {
map.put(key, value);
}
@Override
public String getClassName() {
return "MapScope";
}
}
}
Данный скрипт выводит {b=20, c=30.0, a=10}
, показывающий переменную c
была сохранена в Map
.
Теперь мне нужно перенести эту Java 8 и использовать Nashorn. Тем не менее, я нахожу, что Nashorn всегда хранит глобальные переменные в специальном объекте "nashorn.global"
. Фактически, похоже, что все привязки обрабатываются как доступные только для чтения, а попытки изменить существующую переменную вместо этого приводят к новой глобальной переменной, затеняющей существующую привязку.
public class Nashorn {
private final static ScriptEngineManager MANAGER = new ScriptEngineManager();
public static void main(String[] args) throws ScriptException {
new Nashorn().testBindingsAsArgument();
new Nashorn().testScopeBindings("ENGINE_SCOPE", ScriptContext.ENGINE_SCOPE);
new Nashorn().testScopeBindings("GLOBAL_SCOPE", ScriptContext.GLOBAL_SCOPE);
}
private ScriptEngine engine = MANAGER.getEngineByName("nashorn");
private Map<String, Object> map = new HashMap<>();
private Bindings bindings = new SimpleBindings(map);
private Nashorn() {
map.put("a", 10);
map.put("b", 20);
}
private void testBindingsAsArgument() throws ScriptException {
System.out.println("Bindings as argument:");
engine.eval("c = a + b; a += b", bindings);
System.out.println("map = " + map);
System.out.println("eval('c', bindings) = " + engine.eval("c", bindings));
System.out.println("eval('a', bindings) = " + engine.eval("a", bindings));
}
private void testScopeBindings(String scope_name, int scope) throws ScriptException {
System.out.println("\n" + scope_name + ":");
engine.getContext().setBindings(bindings, scope);
engine.eval("c = a + b; a += b");
System.out.println("map = " + map);
System.out.println("eval('c') = " + engine.eval("c"));
System.out.println("eval('a') = " + engine.eval("a"));
}
}
Выход:
Bindings as argument:
map = {a=10, b=20, nashorn.global=[object global]}
eval('c', bindings) = 30.0
eval('a', bindings) = 30.0
ENGINE_SCOPE:
map = {a=10, b=20, nashorn.global=[object global]}
eval('c') = 30.0
eval('a') = 30.0
GLOBAL_SCOPE:
map = {a=10, b=20}
eval('c') = 30.0
eval('a') = 30.0
В eval
выходных линий показывают результаты правильно вычислены и хранятся, но map
выходные линии показывают результаты не сохраняются, где я хочу им быть.
Это неприемлемо по ряду причин. Отдельные объекты не получают вычисляемые параметры, сохраненные в их собственном локальном хранилище. Переменные из других скриптов, выполняемых на других объектах, будут перенесены из предыдущих исполнений сценариев, что может скрыть логические ошибки (сценарий может случайно использовать неопределенное имя переменной, но если это имя фактически использовалось предыдущим скриптом, старое значение мусора могло бы вместо ReferenceError
, спрятал ошибку).
После engine.eval()
с map.put("c", engine.get("c"))
переместите результат туда, где он мне нужен, но с произвольным сценарием я не знаю, как будут все имена переменных, так что это не вариант.
Итак, вопрос: есть ли способ захватить создание глобальных переменных и сохранить их вместо объекта Java под управлением приложения, например исходного объекта Binding?
Это так глупо, что мы должны прибегнуть к этому, двигатель nashorn так же упрям, как и его создатели, которые делают ставку ... – grimmeld
Вызов выражения внутри IIFE также делает трюк (вам нужно использовать ключевое слово 'var' для объявления переменные). Кто-нибудь знает, есть ли у этого метода какие-либо оговорки? – faizan
@faizan Спасибо за предложение. Использование IIFE с 'var c = ...' не позволяет хранить 'c' в глобальной области. Это помогает изолировать код, не позволяя различным сценариям случайно видеть значения, вычисленные в других сценариях. Но это не помогает с исходной проблемой: фактически захватывает вычисленные значения, хранящиеся в этих переменных в «Карта», предоставляемые программой Java, выполняющей эти фрагменты кода JavaScript, для использования позже в других частях программы. – AJNeufeld