Oracle schlägt Verlagerung von Java EE in die Eclipse Foundation vor

Seit nunmehr fast 20 Jahren ist die Java EE eine verlässliche Bank für den Aufbau und Betrieb von Enterprise-Anwendungen. Oracle – zuvor Sun – hat am Erfolg der Plattform zweifellos einen wesentlichen Anteil, zusammen mit den vielen Firmen, Einzelpersonen und der Community, die sich für Standards und deren Weiterentwicklung engagiert haben.

Nun hat sich die Welt aber weiter gedreht – manchmal scheint es, mit stetig wachsender Geschwindigkeit – und neue Anforderungen (Cloud, Microservices, … – you name it) verlangen nach einer Anpassung und Weiterentwicklung der Plattform in einer Taktung, die nicht mehr so recht zu einem in gewisser Weise zentralistischen Modell passen will.

Vor diesem Hintergrund erscheint die Ankündigung von Oracle, Java EE in die Eclipse Foundation zu verschieben, sehr sinnvoll. Sie ist vielleicht sogar überfällig, wenn man die Unsicherheit und Querelen der vergangenen Monate und Jahre betrachet.

Ich bin sehr zuversichtlich, dass dieser Schritt der richtige ist, um Java EE weiter verlässlich, stabil und zukunftsorientiert auszurichten.

Advertisements

Brauchen wir noch Java EE? (Oder: Magengrummeln auf der JAX)

Ich sitze gerade auf der JAX in Mainz und versuche mich vorsichtig wieder abzuregen. Aber der Reihe nach.

Auf Konferenzen hört man viele Buzz Words – da ist die JAX keine Ausnahme. Also hört man mehrere Zig Mal pro Tag die Worte Microservices und Container. Auch wenn die genaue Definition von ersterem nicht festgelegt ist, so meint man damit die Erstellung (oder auch Aufteilung) von Enterprise-Anwendungen in überschaubare Teile – im Sinne von DDD irgendetwas zwischen Aggregat und Bounded Context. Es handelt sich also um ein Pattern, das die Kopplung von Softwareteilen aus einem monolithischen Ansatz herauslöst und in die (Betriebs-) Infrastruktur verlagert. Dadurch gewinnt man zweifellos Flexibilität, kann also Anwendungsteile unabhängig voneinander deployen, unterschiedlich skalieren, ggf. sogar in unterschiedlichen Sprachen entwickeln. Das kommt natürlich nicht ohne Kosten wie Kommunikationsaufwand, Resilience, Schnittstellenevolution etc. Container in dem genannten Kontext meint in nahezu allen Fällen Docker, in aller Regel mit einer passenden Verwaltung und Orcherstrierung wie Kubernetes, OpenShift o.ä.

Das ist alles verständlich, spannend und nachvollziehbar. Was allerdings in meinem Augen ziemlich nervt, ist die allgegenwärtige Vermischung von Programm- und Betriebsmodell: Es erscheint als unumstößlliche Gewissheit, dass die alten, großen (und schlechten?)  monolithischen Anwendungen auf schwergewichtigen Java-EE-Servern betrieben werden, während man in einem Docker-Container nur leichtgewichtige (?) SE-Anwendungen laufen lassen kann, die mit allem anderen als Java EE entwickelt werden.

Dies wurde auch gerade in großen Teilen einer Konferenz-Session dargestellt, die den gleichen Titel trägt wie dieser Blog-Post. Ein paar Kernaussagen daraus:

  • „Kernbestandteile von Java EE sind Web (aka JSF), EJB, JCA, JMS. CDI ist als Bindeglied dabei“
    Das verkennt in meinen Augen nahezu komplett die Mächtigkeit von CDI. Dieser Standard/Container kann weit mehr als nur EJBs untereinander und mit der Web-UI zu verbinden. Über seine portable Extension-Schnittstelle ist eine fast grenzenlose Anpassung der gesamten Plattform möglich.
  • „Services baut man in Java EE mit EJB“
    Oder eben CDI, wenn man die speziellen Eigenschaften von EJB nicht benötigt (Timer, Remoting etc.). Wobei EJBs ja auch nicht schwergewichtig sind. Wer das behauptet, hat seit Java EE 5 (2009!) oder 6 nicht mehr drauf geschaut. Ein wesentlicher Punkt in Richtung Einfachheit ist doch Dependency Injection. Die kam für EJB mit der Version 3. Und seit 3.1 kann man auf Local Interfaces verzichten. Heute unterscheiden sich EJB und CDI Beans im Programmcode nur marginal und auch zur Laufzeit sind keine global signifikanten Vor- oder Nachteile zu verzeichnen.
  • „Mit Java-EE kann man nur auf relationale DBs zugreifen“
    Hä? Es gibt zwar kein Standard-API zum Zugriff auf NoSQL-DBs. Die sind ja ihrerseits auch noch gar nicht standardisiert. Aber man man doch das jeweilige proprietäre API benutzen!
  • „Die Struktur einer Java-EE-Anwendung ist starr und fix verknüpft, während in einem Container(-Cluster)  Services dynamisch verbunden und skaliert werden können“ (Man stelle sich dazu links eine Grafik eines Application Servers der Jahrtausendwende vor, rechts ein Schaubild einer aktuellen Kubernetes-Landschaft)
    Das vergleicht nun wirklich Äpfel mit Birnen (oder wie der Speaker es ausdrückte: Rüben mit Kraut). Das eine ist eine aus einem Build herausgefallene, monolithische Anwendung, das andere kleine Einzelservices in separaten Deployments. Warum sollten die Services auf der rechten Seite denn nicht mit dem Java-EE-Stack aufgebaut sein? Ein kleiner Service aus JAX-RS, CDI und JPA ist mit Java EE schnell geschrieben und läuft problemlos in einem Docker-Container. Auch der Footprint der Anwendung mit ca. 100 MB ist nicht wirklich schwergewichtig, auch wenn an der einen oder anderen Laufzeitumgebung vielleicht noch optimiert werden kann.
  • „Ihr müsst jetzt nicht auf Java EE verzichten, wenn eure Ansprüche an Durchsatz und Geschwindigkeit in einem niedrigen oder mittleren Bereich sind“
    Mal abgesehen davon, dass dieser Satz so ähnlich ist wie der, den man auf Sprüche-Karten findet („Das kannste schon so machen, aber dann isses halt Kacke“): Limitierungen in diesem Bereich liegen bspw. in synchronen Konzepten. So belegt ein JDBC-Treiber einen Thread, auch wenn er auf die Reaktion der DB wartet. Das lässt sich mit Aufteilung und Containern nicht verändern. Und im Web(-service)-Bereich bietet Java EE auch asynchrone Vorgehensweisen.

Ich will dem Speaker hier aber gar nicht unterstellen, dass er das so gemeint hat, wie ich (und nicht nur ich) das verstanden habe. Er hat nur den falschen Titel für seine Session gewählt: „Brauchen wir noch Java-EE-Server?“ wäre besser gewesen, denn in meinen Augen geht es ausschließlich um das damit verbundene Deployment- und Betriebskonzept. Das kann kann kritisieren, wenn man möchte. Allerdings läuft ein WildFly oder Payara auch wunderbar in einem Docker-Container – kaum unterscheidbar von einem alternativ darin liegenden Fat JAR.

Also: Tun wir uns allen den Gefallen, nicht die Spezifikation Java EE mit den darin enthaltenen Teilen JAX-RS, CDI etc. und die mögliche Ablaufumgebung Java-EE-Server zu vermischen.

Oder auch (mit einer kleinen Anleihe an eine bekannte Fernsehwerbung von früher): Nichts ist unmööööglich mit Java EE und Docker!

Rightsize your Services mit WildFly Swarm

WildFly Swarm

Überblick

Klassische Application Server wie JBoss/WildFly oder Glassfish/Payara sind etablierte und ausgereifte Ablaufumgebungen für Enterprise-Applikationen. Dass mehrere Anwendungen bis zu einem gewissen Grade voneinander isoliert in einem Server berieben werden können, wird in der Praxis wegen der unvermeidlichen Gemeinsamnutzung von Java VM, CPU, Memory etc. selten ausgenutzt. Man trifft also meist Server an, auf denen genau eine Anwendung deployt ist. Durch den Trend zu Microservices kommt es zu einer deutlichen Vergrößerung der Serveranzahl mit den daraus resultierenden Anforderungen im Bereich Installation, Konfiguration und Betrieb. Populäre Alternativen zu Java EE – z. B. Spring und Dropwizard – nutzen ein anderes Betriebsmodell: Statt Anwendungen in jeweils einen Server zu deployen, wird der Server in die Anwendungen integriert. Für die Java-EE-Welt bietet das Open-Source-Projekt WildFly Swarm (wildfly-swarm.io>) einen solchen Ansatz.

Komposition der Runtime

WildFly Swarm ist vergleichbar mit einem in Stücke zerteilten WildFly Application Server. Diese Stücke – sog. Fractions – werden als Maven Dependencies zur Anwendung hinzugefügt:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.wildfly.swarm</groupId>
      <artifactId>bom-all</artifactId>
      <version>${version.wildfly.swarm}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.wildfly.swarm</groupId>
    <artifactId>jaxrs-cdi</artifactId>
  </dependency>
  <dependency>
    <groupId>org.wildfly.swarm</groupId>
    <artifactId>jpa</artifactId>
  </dependency>
  ...

Die derzeit (Dez. 2016) aktuelle Version ist 2016.12.0.

Aus den so gewählten Fractions und dem eigentlichen Anwendungscode wird dann ein Uber Jar gebaut. Dies geschieht mit Hilfe eines Plugins in der Maven-Konfiguration des Projektes:

<build>
  <plugins>
    <plugin>
      <groupId>org.wildfly.swarm</groupId>
      <artifactId>wildfly-swarm-plugin</artifactId>
      <version>${version.wildfly.swarm}</version>
      <configuration>
        <mainClass>de.gedoplan.micro.bootstrap.SwarmBootstrap</mainClass>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>package</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Die o. a. Main Class dient der programmatischen Konfiguration der Anwendung (s. u.). In einfachen Fällen wird sie nicht benötigt. Das entsprechende Element der Plugin-Konfiguration entfällt dann.

Durch den Build-Lauf (z. B. per mvn package) entsteht dann im target-Verzeichnis neben dem normalen Zielartefakt xyz.war eine Datei namens xyz-swarm.jar, die die Anwendung inkl. Server Runtime enthält.

Konfiguration der Anwendung

Wenn die Default-Konfiguration nicht ausreicht, weil bspw. DB-Verbindungen eingerichtet werden müssen, kann eine individuell prgrammierte Main-Klasse genutzt werden. Darin können die einzelnen Fractions mit Hilfe eines Fluent API parametrisiert werden. Alternativ kann eine externe Konfigurationsdatei referenziert werden:

public class SwarmBootstrap {

  public static void main(String[] args) throws Exception {
    Swarm swarm = new Swarm();

    System.setProperty("swarm.context.path", "micro-ohne-server");

    URL standaloneFullXml = SwarmBootstrap.class.getClassLoader().getResource("configuration/standalone-full.xml");

    swarm
        .withXmlConfig(standaloneFullXml)
        .start()
        .deploy();
  }

Die im Beispiel genutzte externe Konfigurationsdatei entspricht der vom WildFly Application Server bekannten standalone-full.xml, muss aber nur die für die zu konfigurierenden Fractions benötigten Einstelldaten enthalten.

Anwendungsstart

Die Anwendung wird durch Ausführung des durch das o. a. Plugin erzeugte Uber Jar gestartet: java -jar target/xyz-swarm.jar.

Weitere Infos

Anwendungskonfiguration mit Apache Tamaya

Nahezu jede Anwendung muss irgendwie konfiguriert werden – seien es Namen, die statt im Code fest eingebaut zu werden, aus einem File gelesen werden sollen, seien es Berechnungsparameter oder URLs. Java (EE) bietet dazu bislang keinen Standard an, so dass jeder sich „sein eigenes Süppchen kochen“ muss.

Nun zeigt sich ein Silberstreif am Horizont in Form eines API zur Konfiguration von Anwendungen, das auf der diesjährigen Java One als Bestandteil von Java EE 8 angekündigt wurde und das Ideen aus dem entsprechenden Teil von Apache DeltaSpike und von Apache Tamaya übernehmen soll. Letzteres soll im Folgenden kurz beschrieben werden.

Apache Tamaya (tamaya.incubator.apache.org) befindet sich derzeit noch in einem sehr frühen Stadium, bedient sich aber bewährter Ideen aus Apache DeltaSpike Configuration und ist somit durchaus in Grenzen bereits einsetzbar.

Apache Tamaya ist modular aufgebaut. Der Core-Anteil wird immer benötigt. Darüber hinaus gibt es diverse Extensions wie bspw. zur Bereitstellung von Konfigurationswerten per CDI Injection. Für Maven-Projekte sind dies die folgenden Dependencies:

<dependency>
  <groupId>org.apache.tamaya</groupId>
  <artifactId>tamaya-core</artifactId>
  <version>${version.tamaya}</version>
</dependency>
<dependency>
  <groupId>org.apache.tamaya.ext</groupId>
  <artifactId>tamaya-cdi</artifactId>
  <version>${version.tamaya}</version>
</dependency>

Derzeit (Dez. 2016) ist die aktuelle Version 0.2-incubating.

Konfigurationswerte können mit einem einfachen API abgeholt werden:

String javaVendor 
  = ConfigurationProvider.getConfiguration()
                         .getOrDefault("java.vendor", "unknown");

ConfigurationProvider.getConfiguration() liefert dabei das Einstiegsobjekt vom Typ Configuration, das neben der oben genutzten Methode getOrDefault auch solche anbietet, die den Wert in einen anderen Typ als String gewandelt liefern.

In CDI Beans kann zudem per Injektion auf die Werte zugegriffen werden:

@Inject
@Config(defaultValue = "unknown")
String companyName;

@Inject
@Config
int answerToLifeUniverseAndEverything;

@Inject
@Config("java.version")
String javaVersion;

Apache Tamaya holt die Werte per Default aus System Properties, Environment Entries und der Datei META-INF/javaconfiguration.properties im Classpath. Es können weitere Property Sources registriert werden und auch die Suchreihenfolge manipuliert werden.

Weitergehende Informationen finden sich auf der Homepage des Projektes (tamaya.incubator.apache.org). Aufgrund des aktuellen Projektstands ist aber noch viel im Fluss.

Ein Demo-Projekt zum Anschauen und Ausprobieren gibt es hier: github.com/GEDOPLAN/config-demo.

Microservices und das Uberjar

Ich habe gerade auf der W-JAX Thilo Frotschers Vortrag über Java EE Microservices verfolgt. Er stellte darin dar, dass bspw. mit Payara Micro sog. Uberjars gebaut werden können, die neben der Microservice-Anwendung auch die notwendige Server-Implementierzung enthalten. Motivation und Ziel: Start der Anwendung inkl. Umgebung als ausführbares Jar auf der Kommandozeile.

Ein Teilnehmer stellte dann die Frage „Muss ich denn für jede Instanz meines Microservices immer die komplette Runtime haben – mit dem entsprechnden Platzbedarf, Startzeiten etc.? Oder könnte ich auch eine Shared Runtime nutzen?“.

Das zeigt – wie ich meine – die derzeitige etwas unglückliche Vermischung der Diskussion über das Programmmodell für Anwendungen und ihr Betriebsmodell. Das was der Fragende nutzen könnte, wäre ein klassischer Application Server!

Wichtig ist aus meiner Sicht, dass die innere Struktur der Anwendungen nicht durch das Betriebskonzept eingeschränkt werden sollte. Das ist mit Java EE der Fall: Ich kann klassische War Files bauen und auf einem klassischen Application Server deployen – gerne auch in Umgebungen wie Docker, wo ebendieses Deployment auch nichts anderes ist als eine Datei an einen bestimmten Platz zu legen. Ich kann aber auch die gleiche Anwendung mit einer Server Runtime zu einem Uberjar kombinieren und dann als eine Kommandozeilen-Anwendung betreiben.

Zu dem Thema gibt es am 08.12.16 in unserem Hause einen kostenlosen Vortrag: http://gedoplan-it-consulting.de/expertenkreis-java/aktuelles/.

Maven Toolchains: Builds (u. a.) mit konfigurierbarem JDK

Wer mit der Betreuung von Software vertraut ist, die nicht mehr ganz taufrisch ist, kenn das Problem: Der Build muss mit einer älteren Java-Version geschehen, z. B. weil genau die beim Kunden im Einsatz ist oder weil bestimmte (auch nicht mehr ganz taufrische) Tools für aktuelle Java-Versionen nicht mehr zur Verfügung stehen.

Seit der Maven-Version 3.3.1 gibt es dafür eine Unterstützung in Form sog. Toolchains. Einfach gesprochen verbirgt sich dahinter eine Zuordnung von logischen Namen und Versionen zu Installationspfaden im Dateisystem. Dazu muss eine neue Konfigurationsdatei (per Default im Ordner .m2 des Users) angelegt werden:

<toolchains>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.6</version>
    </provides>
    <configuration>
      <jdkHome>C:\Program Files\Java\jdk1.6.0_45</jdkHome>
    </configuration>
  </toolchain>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.8</version>
    </provides>
    <configuration>
      <jdkHome>C:\Program Files\Java\jdk1.8.0_101</jdkHome>
    </configuration>
  </toolchain>
</toolchains>

Leider können in dieser Datei derzeit keine Maven-Properties in der Form ${name} verwendet werden. Wer also seine Java-Home-Pfade in Environment-Variablen hat, muss sie dennoch redundant in toolchains.xml eintragen.

Im pom.xml des Maven-Projekts kann nun auf diese Konfiguration Bezug genommen werden. Dazu muss das maven-toolchains-plugin mit der gewünschten Version konfiguriert werden:

<project ...>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-toolchains-plugin</artifactId>
        <version>1.1</version>
        <executions>
          <execution>
            <goals>
              <goal>toolchain</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <toolchains>
            <jdk>
              <version>1.6</version>
            </jdk>
          </toolchains>
        </configuration>
      </plugin>

Das Plugin prüft am Beginn des Builds, ob die gewünschte Version in toolchains.xml vorhanden ist und hinterläßt die zugehörigen Werte in der internen Maven-Session. Dort holen andere Plugins, u. a. das Compiler-Plugin, sich die für sie benötigten Werte ab.

Toolchains können nicht nur für JDKs aufgebaut werden. Details dazu finden sich in der Beschreibung des Plugins.

Ein Demo-Projekt ist hier verfügbar: https://github.com/GEDOPLAN/maven-toolchains-demo.

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

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:

&lt;dependency&gt;
  &lt;groupId&gt;org.codehaus.groovy&lt;/groupId&gt;
  &lt;artifactId&gt;groovy-jsr223&lt;/artifactId&gt;
  &lt;version&gt;2.4.7&lt;/version&gt;
  &lt;scope&gt;runtime&lt;/scope&gt;
&lt;/dependency&gt;

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(&quot;Script Engine: %s (%s)\n&quot;, scriptEngineFactory.getEngineName(), scriptEngineFactory.getEngineVersion());
  System.out.printf(&quot;  Language: %s (%s)\n&quot;, scriptEngineFactory.getLanguageName(), scriptEngineFactory.getLanguageVersion());

  for (String alias : scriptEngineFactory.getNames())
    System.out.printf(&quot;  Alias: %s\n&quot;, 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(&quot;js&quot;);
scriptEngine.eval(&quot;print('Hello, JavaScript!');&quot;);

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(&quot;netto&quot;, 100);
scriptEngine.put(&quot;steuersatz&quot;, 0.19);

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

Object brutto = scriptEngine.get(&quot;brutto&quot;);

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(&quot;scripts/hello.js&quot;);
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(&quot;scripts/factorial.js&quot;);
scriptEngine.eval(new InputStreamReader(resourceAsStream));
Invocable invocable = (Invocable) this.scriptEngine;
Object fac5 = invocable.invokeFunction(&quot;factorial&quot;, 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(&quot;scripts/showDate.js&quot;);
scriptEngine.eval(new InputStreamReader(resourceAsStream));
Invocable invocable = (Invocable) this.scriptEngine;
invocable.invokeFunction(&quot;showDate&quot;, 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(&quot;scripts/fillList.groovy&quot;);
scriptEngine.eval(new InputStreamReader(resourceAsStream));
List&lt;?&gt; list = (List&lt;?&gt;) scriptEngine.get(&quot;list&quot;);
list.forEach(System.out::println);

Weitere Informationen