Ho un programma Java 7, che carica migliaia di oggetti (componenti), ognuno con molti parametri (memorizzati in un Map
) ed esegue vari script di Rhino su quegli oggetti per calcolare altri parametri derivati che vengono memorizzati di nuovo nell'oggetto Map
. Prima di eseguire ogni script, viene creato un oggetto Scope
, supportato dalla mappa dell'oggetto, che viene utilizzato come ambito JavaScript per la durata dello script.Catturare le variabili globali di Nashorn
Come semplice esempio, il seguente crea un HashMap
con a = 10 e b = 20, ed esegue lo script c = a + b
, che si traduce in c = 30.0
memorizzati indietro nella mappa. Sebbene lo script assomigli alla creazione di una variabile globale c
, l'oggetto Scope
lo acquisisce e lo memorizza nella mappa; altro script eseguito con un diverso Scope
oggetto non vedrà questa variabile:
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";
}
}
}
Lo script sopra uscite {b=20, c=30.0, a=10}
, mostrando la variabile c
immagazzinate nella Map
.
Ora, ho bisogno di migrare questo Java 8 e utilizzare Nashorn. Tuttavia, sto riscontrando che Nashorn memorizza sempre le variabili globali in uno speciale oggetto "nashorn.global"
. Infatti, sembra che tutti i binding vengano trattati come di sola lettura e che i tentativi di modificare una variabile esistente generino invece una nuova variabile globale che ombreggia il binding esistente.
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"));
}
}
uscita:
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
Le linee di uscita eval
mostrano i risultati siano correttamente calcolati e vengono memorizzati, ma le linee di uscita map
mostra i risultati non vengono memorizzati dove desidero essere.
Questo non è accettabile, per una serie di motivi. I singoli oggetti non riportano i parametri calcolati memorizzati nella loro memoria locale. Le variabili da altri script in esecuzione su altri oggetti verranno trasferite dalle precedenti esecuzioni di script, che potrebbero nascondere gli errori logici (uno script potrebbe accidentalmente utilizzare un nome di variabile non definito, ma se quel nome era effettivamente utilizzato da uno script precedente, il vecchio valore di garbage pot essere utilizzato al posto di un ReferenceError
generato, nascondendo l'errore).
In seguito allo engine.eval()
con map.put("c", engine.get("c"))
spostare il risultato nel punto in cui ho bisogno di essere, ma con uno script arbitrario, non so quali sarebbero tutti i nomi delle variabili, quindi non è un'opzione.
Quindi la domanda: c'è comunque da catturare la creazione di variabili globali e memorizzarle invece all'interno di un oggetto Java sotto controllo dell'applicazione, come l'oggetto Binding originale ??
È così stupido che dobbiamo ricorrere a questo, il motore Nashorn è testardo come i suoi creatori. Scommetto ... – grimmeld
Invocare l'espressione all'interno di un IIFE fa anche il trucco (devi usare la parola chiave 'var' per dichiarare variabili). Qualcuno sa se questo metodo ha qualche avvertimento? – faizan
@faizan Grazie per il suggerimento. L'uso di un IIFE con 'var c = ...' mantiene 'c' dall'essere immagazzinato nello scope globale. Ciò aiuta a isolare il codice, impedendo a diversi script di vedere accidentalmente i valori calcolati in altri script. Ma questo non aiuta il problema originale: in realtà catturare i valori calcolati memorizzati in queste variabili nella 'Mappa 'fornita dal programma Java che esegue questi snippits di codice JavaScript, da usare successivamente in altre parti del programma. – AJNeufeld