GEDOPLAN
Java SE

Scripting for Java: JavaScript, Groovy etc. in Java nutzen

Java SE

In Java-Anwendungen können Skripte dynamischer Sprachen wie JavaScript, Groovy etc. aufgerufen werden. Die Basis wurde bereits vor langer Zeit mit dem JSR 223 gelegt: seit Java SE 6 ist die Einbindung der sog. Script Engines nach JSR 223 Teil der Java-Plattform.

Die Art der Skriptsprache ist nicht grundsätzlich eingeschränkt. Besonders interessant sind aber die Sprachen, die auf der Java VM laufen. Sie lassen sich mit der Java-Anwendung sehr eng verknüpfen: Es lassen sich Daten zwische Java und Skript austauschen, Skript-Funktionen aus Java heraus aufrufen und sogar Java-Objekte und Java-Methoden in einem Skript benutzen.

Script Engines

Zur Ausführung eines Skriptes wird eine passende Script Engine benötigt. Seit Java 6 ist eine Engine für JavaScript im Standardumfang von Java enthalten. Diese Rhino genanne Engine in den Versionen 6 und 7 wurde bei Java 8 durch Nashorn abgelöst.

Möchte man weitere Skriptsprachen einsetzen, muss die jeweils passende Implementierungsbibliothek in den Classpath aufgenommen werden. So ist bspw. für Groovy die folgende Maven Dependency nötig:

<dependency>
  <groupId>org.codehaus.groovy</groupId>
  <artifactId>groovy-jsr223</artifactId>
  <version>2.4.7</version>
  <scope>runtime</scope>
</dependency>

Den Einstiegspunkt zur Nutzung eines Skripts stellt die Klasse ScriptEngineManager dar. Mit ihrer Hilfe können zunächst mal die verfügbaren Script Engines aufgelistet werden:

ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
for (ScriptEngineFactory scriptEngineFactory : scriptEngineManager.getEngineFactories()) {
  System.out.printf("Script Engine: %s (%s)\n", scriptEngineFactory.getEngineName(), scriptEngineFactory.getEngineVersion());
  System.out.printf("  Language: %s (%s)\n", scriptEngineFactory.getLanguageName(), scriptEngineFactory.getLanguageVersion());

  for (String alias : scriptEngineFactory.getNames())
    System.out.printf("  Alias: %s\n", alias);
}

Im Beispielprojekt erzeugt dies die folgende Ausgabe:

Script Engine: Oracle Nashorn (1.8.0_72)
  Language: ECMAScript (ECMA - 262 Edition 5.1)
  Alias: nashorn
  Alias: Nashorn
  Alias: js
  Alias: JS
  Alias: JavaScript
  Alias: javascript
  Alias: ECMAScript
  Alias: ecmascript
Script Engine: Groovy Scripting Engine (2.0)
  Language: Groovy (2.4.7)
  Alias: groovy
  Alias: Groovy

Die Namen und Aliase der Script Engines können zur Instanzierung einer konkreten Engine genutzt werden, mit der anschließend Skript-Code ausgeführt werden kann:

ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("js");
scriptEngine.eval("print('Hello, JavaScript!');");

Datenaustausch zwischen Java und Skript

Nun ist das Starten von Skripten zwar interessant, verlöre aber schnell seinen Reiz, wenn man nicht zwischen dem aufrufenden Java-Programm und dem Skript Werte transferieren könnte. Dies ist zunächst einmal möglich mit Hilfe der sog. Bindings, die als Implementierung von Map in der Lage sind, Key-Value-Paare aufzunehmen. Es gibt je ein Bindings-Objekt auf globaler Ebene und in der genutzten Engine, zugreifbar mit der Methode getBindings in ScriptEngineManager bzw. ScriptEngine. Für den einfachen Zugriff auf die Key-Value-Paare gibt es in beiden Interfaces die von Map bekannten Methoden get und put.
Diese Bindings-Einträge lassen sich nun innerhalb des Skripts verwenden. Umgekehrt werden vom Skript erzeugte Werte im Bindings-Objekt der Engine abgelegt. Damit ist ein Austausch von Werten zwischen Java-Programm und Skript möglich:

scriptEngine.put("netto", 100);
scriptEngine.put("steuersatz", 0.19);

scriptEngine.eval("brutto = netto * (1+steuersatz)");

Object brutto = scriptEngine.get("brutto");

Das gezeigte Beispiel arbeitet mit dem Bindings-Objekt der ScriptEngine; die Methoden get und put sind Convenience-Methoden für den Aufruf der jeweils gleichnamigen Methode in getBindings(ScriptContext.ENGINE_SCOPE). Die in einem Bindings-Objekt enthaltenen Werte sind aus Sicht des Skriptes globale Daten vergleichbar mit den im Application Scope bzw. Session Scope einer Web-Anwendung abgelegten Werten.

Ausführung von Skript-Dateien

Einfacher Skript-Code lässt sich wie gezeigt direkt durch Übergabe des Codes als String an die Script Engine ausführen. Für umfangreichere Skripte ist kann statt dessen aber auch ein Reader an ScriptEngine.eval übergeben werden:

InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("scripts/hello.js");
scriptEngine.eval(new InputStreamReader(resourceAsStream));

Aufruf von Skript-Funktionen

Eine weitere Möglichkeit besteht darin, Skripte bzw. Teile daraus als Funktionen aufzurufen. Dazu wird das Skript zunächst einmalig ausgeführt und damit quasi kompiliert. Anschließend können im Skript definierte Funktionen aus dem Java-Programm heraus aufgerufen werden:

// scripts/factorial.js
function factorial(n) {
 return n == 1 ? 1 : n * factorial(n - 1)
}
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("scripts/factorial.js");
scriptEngine.eval(new InputStreamReader(resourceAsStream));
Invocable invocable = (Invocable) this.scriptEngine;
Object fac5 = invocable.invokeFunction("factorial", 5);

In den Beispielen wurden nur primitive Daten bzw. Objekte der entsprechenden Wrapper-Klassen (int bzw. java.lang.Integer) zwischen Java und Skript ausgetauscht. Darauf ist man allerdings nicht eingeschränkt; es können vielmehr beliebige Objekte übergeben werden:

// scripts/showDate.js
function showDate(timestamp) {
  print('Jahr:  ' + timestamp.get(java.util.Calendar.YEAR))
  print('Monat: ' + (timestamp.get(java.util.Calendar.MONTH)+1))
  print('Tag:   ' + timestamp.get(java.util.Calendar.DAY_OF_MONTH))
}
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("scripts/showDate.js");
scriptEngine.eval(new InputStreamReader(resourceAsStream));
Invocable invocable = (Invocable) this.scriptEngine;
invocable.invokeFunction("showDate", Calendar.getInstance());

Nutzung von Java-Objekten im Skript

Da das Skript auf der Java VM läuft, hat es auch zugriff auf die Standardbibliothek (oder andere Klassen im Classpath). Die Syntax zum Zugriff auf Java-Klassen ist natürlich sehr abhängig von der eingesetzten Skriptsprache. Groovy bietet z. B. eine Java-ähnliche Syntax an:

// scripts/fillList.groovy
list = new java.util.ArrayList();
list.add('Hugo')
list.add('Willi')
list.add('Otto')
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("scripts/fillList.groovy");
scriptEngine.eval(new InputStreamReader(resourceAsStream));
List<?> list = (List<?>) scriptEngine.get("list");
list.forEach(System.out::println);

Weitere Informationen

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

pay 937882 640
Application Server

Versionen von WildFly und JBoss EAP

In vielen unserer Seminare (z. B. https://gedoplan.de/it-schulungen/entwicklung-und-betrieb-von-anwendungen-auf-wildfly/) nutzen wir WildFly als Application Server. Unsere Kunden verwenden häufig statt der Open-Source-Distribution…

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!