Einführung in Nashorn

1. Einleitung

Dieser Artikel konzentriert sich auf Nashorn - die neue Standard-JavaScript-Engine für die JVM ab Java 8.

Viele ausgefeilte Techniken wurden verwendet, um Nashorn um Größenordnungen leistungsfähiger zu machen als sein Vorgänger namens Rhino, daher ist es eine lohnende Änderung.

Schauen wir uns einige Möglichkeiten an, wie es verwendet werden kann.

2. Befehlszeile

JDK 1.8 enthält einen Befehlszeileninterpreter namens jjs, mit dem JavaScript-Dateien oder, wenn ohne Argumente gestartet, als REPL (interaktive Shell) ausgeführt werden können:

$ $JAVA_HOME/bin/jjs hello.js Hello World

Hier enthält die Datei hello.js eine einzige Anweisung: print ("Hello World");

Der gleiche Code kann auf interaktive Weise ausgeführt werden:

$ $JAVA_HOME/bin/jjs jjs> print("Hello World") Hello World

Sie können die * nix-Laufzeit auch anweisen, jjs zum Ausführen eines Zielskripts zu verwenden, indem Sie als erste Zeile ein #! $ JAVA_HOME / bin / jjs hinzufügen :

#!$JAVA_HOME/bin/jjs var greeting = "Hello World"; print(greeting);

Und dann kann die Datei wie gewohnt ausgeführt werden:

$ ./hello.js Hello World

3. Embedded Script Engine

Die zweite und wahrscheinlich häufigere Methode zum Ausführen von JavaScript in der JVM ist die ScriptEngine. JSR-223 definiert eine Reihe von Skript-APIs, die eine steckbare Skript-Engine-Architektur ermöglichen, die für jede dynamische Sprache verwendet werden kann (vorausgesetzt, sie verfügt natürlich über eine JVM-Implementierung).

Erstellen wir eine JavaScript-Engine:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); Object result = engine.eval( "var greeting="hello world";" + "print(greeting);" + "greeting");

Hier schaffen wir eine neue Script und sofort fragen sie uns zu geben , ein Script namens nashorn . Dann übergeben wir ein paar Anweisungen und erhalten das Ergebnis, das sich vorhersehbar als StringHallo Welt “ herausstellt .

4. Daten an das Skript übergeben

Daten können an die Engine übergeben werden, indem ein Bindings- Objekt definiert und als zweiter Parameter an die eval- Funktion übergeben wird:

Bindings bindings = engine.createBindings(); bindings.put("count", 3); bindings.put("name", "baeldung"); String script = "var greeting="Hello";" + "for(var i=count;i>0;i--) { " + "greeting+=name + ' '" + "}" + "greeting"; Object bindingsResult = engine.eval(script, bindings);

Wenn Sie dieses Snippet ausführen, wird Folgendes angezeigt : „ Hallo Baeldung Baeldung Baeldung “.

5. Aufrufen von JavaScript-Funktionen

Es ist natürlich möglich, JavaScript-Funktionen aus Ihrem Java-Code aufzurufen:

engine.eval("function composeGreeting(name) {" + "return 'Hello ' + name" + "}"); Invocable invocable = (Invocable) engine; Object funcResult = invocable.invokeFunction("composeGreeting", "baeldung");

Dies wird " Hallo Baeldung " zurückgeben.

6. Verwenden von Java-Objekten

Da wir in der JVM ausgeführt werden, ist es möglich, native Java-Objekte aus JavaScript-Code heraus zu verwenden.

Dies wird mithilfe eines Java- Objekts erreicht:

Object map = engine.eval("var HashMap = Java.type('java.util.HashMap');" + "var map = new HashMap();" + "map.put('hello', 'world');" + "map");

7. Spracherweiterungen

Nashorn zielt auf ECMAScript 5.1 ab , bietet jedoch Erweiterungen, um die Verwendung von JavaScript ein bisschen angenehmer zu gestalten.

7.1. Sammlungen mit For-Each iterieren

For-each ist eine praktische Erweiterung, um die Iteration über verschiedene Sammlungen zu vereinfachen:

String script = "var list = [1, 2, 3, 4, 5];" + "var result = '';" + "for each (var i in list) {" + "result+=i+'-';" + "};" + "print(result);"; engine.eval(script);

Hier verbinden wir Elemente eines Arrays, indem wir für jedes Iterationskonstrukt verwenden.

Die resultierende Ausgabe ist 1-2-3-4-5- .

7.2. Funktionsliterale

In einfachen Funktionsdeklarationen können Sie geschweifte Klammern weglassen:

function increment(in) ++in

Dies ist natürlich nur für einfache Einzeilerfunktionen möglich.

7.3. Bedingte Fangklauseln

Es ist möglich, geschützte catch-Klauseln hinzuzufügen, die nur ausgeführt werden, wenn die angegebene Bedingung erfüllt ist:

try { throw "BOOM"; } catch(e if typeof e === 'string') { print("String thrown: " + e); } catch(e) { print("this shouldn't happen!"); }

Dies wird " String geworfen: BOOM " drucken .

7.4. Typisierte Arrays und Typkonvertierungen

Es ist möglich, Arrays vom Typ Java zu verwenden und in und aus JavaScript-Arrays zu konvertieren:

function arrays(arr) { var javaIntArray = Java.to(arr, "int[]"); print(javaIntArray[0]); print(javaIntArray[1]); print(javaIntArray[2]); }

Nashorn führt hier einige Typkonvertierungen durch, um sicherzustellen, dass alle Werte aus dem dynamisch typisierten JavaScript-Array in die Nur-Ganzzahl-Java-Arrays passen.

Das Ergebnis des Aufrufs der obigen Funktion mit dem Argument [100, "1654", true] führt zur Ausgabe von 100, 1654 und 1 (alle Zahlen).

Die String- und Booleschen Werte wurden implizit in ihre Gegenstücke für logische Ganzzahlen konvertiert.

7.5. Festlegen des Objektprototyps mit Object.setPrototypeOf

Nashorn defines an API extension that enables us to change the prototype of an object:

Object.setPrototypeOf(obj, newProto)

This function is generally considered a better alternative to Object.prototype.__proto__ so it should be the preferred way to set object's prototype in all new code.

7.6. Magical __noSuchProperty__ and __noSuchMethod__

It is possible to define methods on an object that will be invoked whenever an undefined property is accessed or an undefined method is invoked:

var demo = { __noSuchProperty__: function (propName) { print("Accessed non-existing property: " + propName); }, __noSuchMethod__: function (methodName) { print("Invoked non-existing method: " + methodName); } }; demo.doesNotExist; demo.callNonExistingMethod()

This will print:

Accessed non-existing property: doesNotExist Invoked non-existing method: callNonExistingMethod

7.7. Bind Object Properties With Object.bindProperties

Object.bindProperties can be used to bind properties from one object into another:

var first = { name: "Whiskey", age: 5 }; var second = { volume: 100 }; Object.bindProperties(first, second); print(first.volume); second.volume = 1000; print(first.volume);

Notice, that this creates is a “live” binding and any updates to the source object are also visible through the binding target.

7.8. Locations

Current file name, directory and a line can be obtained from global variables __FILE__, __DIR__, __LINE__:

print(__FILE__, __LINE__, __DIR__)

7.9. Extensions to String.prototype

There are two simple, but very useful extensions that Nashorn provides on the String prototype. These are trimRight and trimLeft functions which, unsurprisingly, return a copy of the String with the whitespace removed:

print(" hello world".trimLeft()); print("hello world ".trimRight());

Will print “hello world” twice without leading or trailing spaces.

7.10. Java.asJSONCompatible Function

Using this function, we can obtain an object that is compatible with Java JSON libraries expectations.

Namely, that if it itself, or any object transitively reachable through it is a JavaScript array, then such objects will be exposed as JSObject that also implements the List interface for exposing the array elements.

Object obj = engine.eval("Java.asJSONCompatible( { number: 42, greet: 'hello', primes: [2,3,5,7,11,13] })"); Map map = (Map)obj; System.out.println(map.get("greet")); System.out.println(map.get("primes")); System.out.println(List.class.isAssignableFrom(map.get("primes").getClass()));

This will print “hello” followed by [2, 3, 5, 7, 11, 13] followed by true.

8. Loading Scripts

It's also possible to load another JavaScript file from within the ScriptEngine:

load('classpath:script.js')

A script can also be loaded from a URL:

load('/script.js')

Keep in mind that JavaScript does not have a concept of namespaces so everything gets piled on into the global scope. This makes it possible for loaded scripts to create naming conflicts with your code or each other. This can be mitigated by using the loadWithNewGlobal function:

var math = loadWithNewGlobal('classpath:math_module.js') math.increment(5);

With the following math_module.js:

var math = { increment: function(num) { return ++num; } }; math;bai

Here we are defining an object named math that has a single function called increment. Using this paradigm we can even emulate basic modularity!

8. Conclusion

In diesem Artikel wurden einige Funktionen der Nashorn J- AvaScript-Engine erläutert. In den hier vorgestellten Beispielen wurden String-Literal-Skripte verwendet. In realen Szenarien möchten Sie Ihr Skript jedoch höchstwahrscheinlich in separaten Dateien aufbewahren und mithilfe einer Reader- Klasse laden .

Wie immer ist der Code in diesem Artikel auf GitHub verfügbar.