2016-03-04 9 views
5

У меня есть программа 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?

ответ

3

У меня есть решение, которое, похоже, работает, но это явно взломан.

Программа испытаний:

public class Nashorn { 
    public static void main(String[] args) throws ScriptException { 
     ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); 

     Map<String, Object> map = new HashMap<>(); 
     map.put("a", 10); 
     map.put("b", 20); 

     try (GlobalMap globals = new GlobalMap(map)) { 
      engine.eval("c = a + b; a += b;", globals); 
     } 

     System.out.println("map = " + map); 
    } 
} 

тестовая программа выдает map = {a=30.0, b=20, c=30.0} по желанию.

GlobalMap перехватывает хранение глобального объекта Nashorn под ключ "nashorn.global", поэтому он не сохраняется на карте.Когда GlobalMap закрыт, он удаляет любые новые глобальные переменные из глобального объекта Насхорн и сохраняет их в оригинальной карте:

public class GlobalMap extends SimpleBindings implements Closeable { 

    private final static String NASHORN_GLOBAL = "nashorn.global"; 
    private Bindings global; 
    private Set<String> original_keys; 

    public GlobalMap(Map<String, Object> map) { 
     super(map); 
    } 

    @Override 
    public Object put(String key, Object value) { 
     if (key.equals(NASHORN_GLOBAL) && value instanceof Bindings) { 
      global = (Bindings) value; 
      original_keys = new HashSet<>(global.keySet()); 
      return null; 
     } 
     return super.put(key, value); 
    } 

    @Override 
    public Object get(Object key) { 
     return key.equals(NASHORN_GLOBAL) ? global : super.get(key); 
    } 

    @Override 
    public void close() { 
     if (global != null) { 
      Set<String> keys = new HashSet<>(global.keySet()); 
      keys.removeAll(original_keys); 
      for (String key : keys) 
       put(key, global.remove(key)); 
     } 
    } 
} 

Я все еще в надежде найти решение, при котором текущая область может быть установлен на Map<String,Object> или Bindings, и любые новые переменные, созданные скриптом, хранятся непосредственно в этом объекте.

+0

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

+2

Вызов выражения внутри IIFE также делает трюк (вам нужно использовать ключевое слово 'var' для объявления переменные). Кто-нибудь знает, есть ли у этого метода какие-либо оговорки? – faizan

+0

@faizan Спасибо за предложение. Использование IIFE с 'var c = ...' не позволяет хранить 'c' в глобальной области. Это помогает изолировать код, не позволяя различным сценариям случайно видеть значения, вычисленные в других сценариях. Но это не помогает с исходной проблемой: фактически захватывает вычисленные значения, хранящиеся в этих переменных в «Карта», предоставляемые программой Java, выполняющей эти фрагменты кода JavaScript, для использования позже в других частях программы. – AJNeufeld

Смежные вопросы