2015-11-09 8 views
5

Ho un runtime di Nashorn personalizzato che ho impostato con alcune funzioni e oggetti globali - alcuni di questi sono senza stato e alcuni di questi sono di stato. Contro questo runtime, sto eseguendo alcuni script personalizzati.Esecuzione di una funzione in un contesto specifico in Nashorn

Per ogni esecuzione, sto progettando sulla creazione di un nuovo contesto che è sostenuta dal contesto globale:

myContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); 
engine.eval(myScript, myContext); 

In base a quello che ho letto, eventuali modifiche alla portata globale (dal punto di vista dello script) sarà limitato al nuovo contesto che ho creato.

Questi script, una volta valutato, espongono alcuni oggetti (con nomi ben definiti e nomi di metodi). Posso richiamare un metodo sull'oggetto mediante il casting di engine in Invocable. Ma come faccio a sapere il contesto in cui verrà eseguita la funzione? Si tratta di un problema o il contesto di esecuzione di tale funzione è impostato in base al contesto in cui è stato valutato?

Quale comportamento posso aspettarmi in una situazione con multithreading in cui tutti i thread condividono la stessa istanza del motore di script e tutti cercano di eseguire lo stesso script (che espone un oggetto globale). Quando invoco il metodo sull'oggetto, in quale contesto verrà eseguita la funzione? Come saprà quale istanza dell'oggetto usare?

Mi aspettavo di vedere un metodo invoke in cui è possibile specificare il contesto, ma questo non sembra essere il caso. C'è un modo per farlo o sto sbagliando tutto questo?

So che un modo semplice per aggirare questo è creare una nuova istanza di script-engine per esecuzione, ma come ho capito, perderei le ottimizzazioni (specialmente sul codice condiviso). Detto questo, potrebbe precompilare l'aiuto qui?

+0

Ho letto che la pre-compilazione è in realtà un no-op in Nashorn, quindi non aiuta. Non riesci a trovare la fonte però. Potrei sbagliarmi. –

risposta

9

L'ho capito. Il problema stavo correndo in era che invokeFunction sarebbe gettare un NoSuchMethodException perché le funzioni esposte dal script personalizzato non esistevano negli attacchi dal campo di applicazione di default del motore:

ScriptContext context = new SimpleScriptContext(); 
context.setBindings(nashorn.createBindings(), ScriptContext.ENGINE_SCOPE); 
engine.eval(customScriptSource, context); 
((Invocable) engine).invokeFunction(name, args); //<- NoSuchMethodException thrown 

Quindi quello che dovevo fare era tirare fuori la funzione dal contesto per nome e lo chiamano esplicitamente in questo modo:

JSObject function = (JSObject) context.getAttribute(name, ScriptContext.ENGINE_SCOPE); 
function.call(null, args); //call to JSObject#isFunction omitted brevity 

Ciò chiamare la funzione che esiste nel vostro contesto appena creato. È inoltre possibile richiamare metodi su oggetti in questo modo:

JSObject object = (JSObject) context.getAttribute(name, ScriptContext.ENGINE_SCOPE); 
JSObject method = (JSObject) object.getMember(name); 
method.call(object, args); 

call genera un'eccezione incontrollato (sia Throwable avvolto in un RuntimeException o NashornException che è stato inizializzato con JavaScript informazioni stackframe) in modo da avere per gestire in modo esplicito che se si voglio fornire un feedback utile.

In questo modo i thread non possono sovrapporsi perché esiste un contesto separato per thread. Sono stato anche in grado di condividere codice runtime personalizzato tra i thread e garantire che le modifiche di stato agli oggetti mutabili esposti dal runtime personalizzato fossero isolate dal contesto.

Per fare questo, ho creare un'istanza CompiledScript che contiene una rappresentazione compilata di mia abitudine runtime-biblioteca:

public class Runtime { 

    private ScriptEngine engine; 
    private CompiledScript compiledRuntime; 

    public Runtime() { 
     engine = new NashornScriptEngineFactory().getScriptEngine("-strict"); 
     String source = new Scanner(
      this.getClass().getClassLoader().getResourceAsStream("runtime/runtime.js") 
     ).useDelimiter("\\Z").next(); 

     try { 
      compiledRuntime = ((Compilable) engine).compile(source); 
     } catch(ScriptException e) { 
      ... 
     } 
    } 

    ... 
} 

Poi, quando ho bisogno di eseguire uno script valuto la sorgente compilato, e poi valutare la script in quel contesto così:

ScriptContext context = new SimpleScriptContext(); 
context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); 

//Exception handling omitted for brevity 

//Evaluate the compiled runtime in our new context 
compiledRuntime.eval(context); 

//Evaluate the source in the same context 
engine.eval(source, context); 

//Call a function 
JSObject jsObject = (JSObject) context.getAttribute(function, ScriptContext.ENGINE_SCOPE); 
jsObject.call(null, args); 

ho provato questo con più thread e sono stato in grado di fare in modo che i cambiamenti di stato sono stati limitati ai contesti che appartengono a singoli thread. Questo perché la rappresentazione compilata viene eseguita all'interno di un contesto specifico, il che significa che le istanze di qualsiasi cosa esposta da esso sono portate a tale contesto.

Un piccolo svantaggio è che è possibile rivalutare inutilmente le definizioni di oggetti per oggetti che non devono avere uno stato specifico del thread. Per aggirare il problema, valutarli sul motore direttamente, a cui si aggiungeranno le associazioni per quegli oggetti al motore ENGINE_SCOPE:

public Runtime() { 
    ... 
    String shared = new Scanner(
     this.getClass().getClassLoader().getResourceAsStream("runtime/shared.js") 
    ).useDelimiter("\\Z").next(); 

    try { 
     ...   
     nashorn.eval(shared); 
     ... 
    } catch(ScriptException e) { 
     ... 
    } 
} 

Poi, più tardi, è possibile popolare del contesto specifico in filo del motore ENGINE_SCOPE:

context.getBindings(ScriptContext.ENGINE_SCOPE).putAll(engine.getBindings(ScriptContext.ENGINE_SCOPE)); 

Una cosa che dovrai fare è assicurarti che tutti gli oggetti che esponi siano stati congelati. Altrimenti è possibile ridefinire o aggiungere proprietà a loro.

+2

Grazie per aver dedicato del tempo per rispondere alla tua stessa domanda. Molto utile! –

+0

Domanda però. Qual è la differenza tra la valutazione dello script compilato e la fonte? Mi sembra che sia doppio? –

+0

L'ho implementato in base a questo, ma ho lasciato fuori 'engine.eval (source, context)' e sembra funzionare bene. –

Problemi correlati