2015-11-09 6 views
5

Ich habe eine benutzerdefinierte Nashorn-Laufzeit, die ich mit einigen globalen Funktionen und Objekten eingerichtet habe - einige davon sind zustandslos und einige davon sind Stateful. Gegen diese Laufzeit laufen einige benutzerdefinierte Skripts.Ausführen einer Funktion in einem bestimmten Kontext in Nashorn

Für jede Ausführung, ich plane einen neuen Kontext zu schaffen, die von dem globalen Kontext unterstützt wird:

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

Nach dem, was ich gelesen globalen Bereich, alle Änderungen (aus Sicht des Skripts) wird auf den neuen Kontext beschränkt, den ich erstellt habe.

Diese Skripts zeigen bei der Auswertung einige Objekte (mit gut definierten Namen und Methodennamen) an. Ich kann eine Methode für das Objekt aufrufen, indem engine zu Invocable umgewandelt wird. Aber woher weiß ich den Kontext, in dem die Funktion ausgeführt wird? Ist das überhaupt ein Problem, oder ist der Ausführungskontext dieser Funktion basierend auf dem Kontext festgelegt, in dem sie ausgewertet wurde?

Welches Verhalten kann ich in einer Multithread-Situation erwarten, in der alle Threads die gleiche Skript-Engine-Instanz verwenden und alle versuchen, dasselbe Skript auszuführen (wodurch ein globales Objekt verfügbar wird). Wenn ich dann die Methode für das Objekt aufruft, in welchem ​​Kontext wird die Funktion ausgeführt? Woher weiß es, welche Instanz des Objekts verwendet werden soll?

Ich erwarte eine invoke Methode, um zu sehen, wo ich den Kontext angeben kann, aber dies scheint nicht der Fall zu sein. Gibt es eine Möglichkeit, dies zu tun, oder mache ich das völlig falsch?

Ich weiß, dass ein einfacher Weg, um dies zu umgehen ist, eine neue Skript-Engine-Instanz pro Ausführung zu erstellen, aber wie ich verstehe, würde ich Optimierungen verlieren (vor allem auf den gemeinsamen Code). Würde das vorkompilieren hier helfen?

+0

Ich habe gelesen, dass Pre-Compiling ist eigentlich ein No-Op in Nashorn, so dass es nicht hilft. Die Quelle kann jedoch nicht gefunden werden. Ich könnte falsch liegen. –

Antwort

9

Ich habe das herausgefunden. Das Problem, das ich in lief war, dass invokeFunction ein NoSuchMethodException werfen würde, weil die von der benutzerdefinierten Skript ausgesetzten Funktionen nicht von dem Standardbereich des Motors in den Bindungen vorhanden waren:

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

Also, was ich aus zu tun hatte, wurde zu ziehen die Funktion aus dem Kontext namentlich es und explizit nennen wie so:

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

Dies wird die Funktion aufrufen, die in Ihrem Kontext neu erstellte existiert. Sie können auch Methoden auf Objekte auf diese Weise aufrufen:

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

call einen ungeprüften Ausnahme auslöst (entweder Throwable in einem gewickelten RuntimeException oder NashornException, die mit JavaScript StackFrame- Informationen initialisiert wurde), so können Sie explizit behandeln müssen, dass, wenn Sie möchte nützliches Feedback geben.

Auf diese Weise können sich Threads nicht gegenseitig übertreten, da es einen separaten Kontext pro Thread gibt. Ich war auch in der Lage, benutzerdefinierten Laufzeitcode zwischen den Threads freizugeben und sicherzustellen, dass Statusänderungen an veränderbaren Objekten, die von der benutzerdefinierten Laufzeitumgebung verfügbar gemacht wurden, durch Kontext isoliert wurden.

Um dies zu tun, ich eine CompiledScript Instanz, die eine kompilierte Darstellung meiner benutzerdefinierten Runtime-Bibliothek enthält:

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) { 
      ... 
     } 
    } 

    ... 
} 

Dann, wenn ich ein Skript ausführen muss ich die kompilierte Quelle zu bewerten und dann die Evaluierung Skript gegen diesen Zusammenhang auch:

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); 

getestet habe ich diese mit mehreren Threads und ich war in der Lage, um sicherzustellen, dass Statusänderungen wurden auf die Kontexte beschränkt, die einzelnen Fäden gehören. Dies liegt daran, dass die kompilierte Repräsentation in einem bestimmten Kontext ausgeführt wird, was bedeutet, dass Instanzen von allem, was von ihr exponiert wird, auf diesen Kontext beschränkt sind.

Ein kleiner Nachteil hier ist, dass Sie Objektdefinitionen für Objekte, die keinen threadspezifischen Zustand haben müssen, unnötig neu bewerten müssen. Um dies zu umgehen, bewerten direkt am Motor, die ENGINE_SCOPE Bindings für diese Objekte auf den Motor hinzufügen wird:

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

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

Dann später, können Sie den Faden spezifischen Kontext aus dem Motor ENGINE_SCOPE bevölkern:

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

Eine Sache, die Sie tun müssen, ist sicherzustellen, dass solche Objekte, die Sie ausgesetzt sind, eingefroren wurden. Ansonsten ist es möglich, Eigenschaften neu zu definieren oder hinzuzufügen.

+2

Vielen Dank, dass Sie sich die Zeit genommen haben, Ihre eigene Frage zu beantworten. Sehr hilfreich! –

+0

Frage obwohl. Was ist der Unterschied zwischen der Auswertung des kompilierten Skripts und der Quelle? Es scheint mir, es ist doppelt? –

+0

Ich habe basierend darauf implementiert, aber 'engine.eval (Quelle, Kontext) weggelassen' und es scheint gut zu funktionieren. –

Verwandte Themen