Das Wissensportal für IT-Professionals. Entdecke die Tiefe und Breite unseres IT-Contents in exklusiven Themenchannels und Magazinmarken.

SIGS DATACOM GmbH

Lindlaustraße 2c, 53842 Troisdorf

Tel: +49 (0)2241/2341-100

kundenservice@sigs-datacom.de

Java-Quellcode ausführen mit JBang

Mit JBang kommt richtiges Skripting zu Java, statt traditioneller Bash-Skripte können jetzt Automatisierungsaufgaben mit den vollen Möglichkeiten von Java inklusive Zusatzbibliotheken und Parallelisierung durchgeführt werden.
Author Image
Michael Hunger

Teamleiter


  • 26.11.2021
  • Lesezeit: 13 Minuten
  • 98 Views

Andere Sprachen wie Python oder JavaScript erlauben es, mit wenig Aufwand und Zeremonie, Skripte für den täglichen Bedarf zu schreiben. Mit Java war das bisher relativ aufwendig.

In diversen Artikeln hatte ich ja schon angemerkt, dass dank [JEP330] seit Java 11 Quellcode direkt mit dem java-Kommando ausgeführt werden kann (s. Listing 1). In Unix-Systemen kann Java sogar als Shell am Anfang der Datei angegeben werden, #!/usr/ bin/java --source 11, dann darf allerdings die Dateiendung nicht auf .java enden.

Leider hat diese Fähigkeit noch einige Einschränkungen, die größte ist die sehr umständliche Bereitstellung von Zusatzbibliotheken (z. B. ein JDBC-Treiber). Diese müssen extra heruntergeladen und im Classpath zur Verfügung gestellt werden. In der Vergangenheit habe ich meist in solchen Fällen auf groovy und die @Grab-Annotation zurückgegriffen, die Bibliotheken automatisch (transitiv) herunterlädt und einbindet. Dafür muss dann aber wieder Groovy installiert sein, und Groovy-Syntax genutzt werden.

Skripte mit Java zu schreiben, hat eine Menge Vorteile, neben der IDE-Unterstützung hat man dank Java-Streams, Java (N)IO und JDBC ein mächtiges Werkzeug in der Hand, um auch komplexere Aufgaben zu bewerkstelligen.

// java Ls.java .
import java.io.*;
public class Ls {
public static void main(String...args) {
 for (var f : new File(args[0]).listFiles())
 System.out.println(f);
 }
}
Listing 1: Beispiel für ein Datei-Listing in Java

Entstehung von JBang

Max Rydahl Andersen (Red Hat Schweiz), dem Entwickler von JBang [jbang], waren diese und andere Probleme ein Dorn im Auge, sodass er beschloss, ein Tool zu veröffentlichen (Januar 2020 [jbang-historie]), das deutlich über JEP330 hinausgeht und umfassendes Java-Skripting erlaubt. Er wollte damit das Lernen, Ausführen, Skripting und Packaging von kleinen Java-Anwendungen und Skripten so einfach wie möglich machen.

Damit kann statt Python oder Go für wiederkehrende Skripte einfach Java genutzt werden, zum Beispiel auch für Plug-ins für Tools wie git oder kubectl. Auch in CI-Tools wie GitHub Actions, dafür steht jbang-action zur Verfügung. Eine praktische Anwendung von JBang ist auch das kompakte Melden von Fehlern, da in einer einzigen Datei sowohl die Fehlerreproduktion als auch notwendige Abhängigkeiten gekapselt werden können. Andere Möglichkeiten bieten sich in kompakten UI-Anwendungen mit Java-FX (s. Listing 2) mittels Gluon oder Interaktion mit Browser (Selenium) oder Desktop (Java Desktop API).

jbang https://git.io/JKPVb
Listing 2: Ausführen eines JFX-Tile-Demos

Das Tool besteht vor allem aus der jbang-Kommandozeilenanwendung, die alle relevanten Funktionen in sich vereint. Zusatzfunktionen wie der App-Store sind optional. Viele Beispiele für solche Anwendungen sind in [jbang-examples] verfügbar.

JBang funktioniert ab Java 8 und steht unter der MIT-Lizenz in Version 0.81.x zur Verfügung. Der Name JBang ist eine Anspielung auf das „Hash-Bang” (#!), das in Shellskripten als Präfix genutzt wird, um einen Interpreter anzugeben.

Erstes JBang-Beispiel

Listing 3 zeigt ein erstes jbang-Beispiel zum Parsen von HTML-Seiten mit JSoup als Bibliothek. Dabei wird die übergebene URL geöffnet und Elemente (z. B. Anchors) werden anhand des angeforderten Selektors gefunden und ausgegeben. Die Abhängigkeit zu JSoup wird als DEPS-Kommentar oben in der Datei vermerkt und beim ersten Ausführen heruntergeladen.

Mittels eines //JAVA 17-Kommentars können wir sogar die gewünschte Java-Version angeben und mit der JVM-Option //JAVA[C]_ OPTIONS konfigurieren. Alternativ kann --java 17 als Kommandozeilenparameter beim Aufruf genutzt werden.

//DEPS org.jsoup:jsoup:1.14.3
//JAVA 17+
//JAVA_OPTIONS --enable-preview
import static java.lang.System.*;
import org.jsoup.*;
public class Html {
public static void main(String...args) throws Exception {
 var doc = Jsoup.connect(args[0]).get();
 out.println(doc.title());
 doc.select(args[1]).stream()
 .map(e -> new Anchor(e.text(), e.attr("href")))
 .forEach(out::println);
}
record Anchor(String text, String href) {}
}
Listing 3: Html-Parser mit JSoup
jbang Html.java "https://en.wikipedia.org/" "#mp-itn b a"
[jbang] Building jar...
Wikipedia, the free encyclopedia
Expo 2020 -> /wiki/Expo_2020
2021 Ryder Cup -> /wiki/2021_Ryder_Cup
74th Tony Awards -> /wiki/74th_Tony_Awards
2021 German federal election -> /wiki/2021_German_federal_election
Detention of Michael Spavor and Michael Kovrig -> /wiki/Detention_of_
Michael_Spavor_and_Michael_Kovrig
Meng Wanzhou -> /wiki/Meng_Wanzhou
Portal:Current events -> /wiki/Portal:Current_events
Deaths in 2021 -> /wiki/Deaths_in_2021
Wikipedia:In the news/Candidates -> /wiki/Wikipedia:In_the_news/
Candidates
Listing 4: Ausführung des Html-Parsers für Wikipedia

Installation

JBang kann am einfachsten über SDKMan installiert werden, ein einfaches sdk install jbang reicht. Auch bei homebrew, scoop, chocolatey und weiteren Paketmanagern ist es verfügbar und auch einfach in jeder Shell (s. Listing 5). Dankenswerterweise wird auf Rechnern, auf denen noch kein Java verfügbar ist, dieses bei der Benutzung von JBang einmalig heruntergeladen und installiert.

# Powershell:
iex "& { $(iwr https://ps.jbang.dev) } app setup"
# GitBash/cygwin/mingwin/WSL:
curl -Ls https://sh.jbang.dev | bash -s - app setup
Listing 5: Installation von jbang mittels der Shell

Entwickeln mit JBang

JBang merkt man an, dass sein Entwickler ein Tool für den eigenen praktischen Einsatz geschaffen hat. Es ist wirklich überlegt und nutzerfreundlich konzipiert. Ein neues JBang-Skript kann mittels jbang init Query.java initialisiert werden, damit bekommt man den relevanten Rumpf geliefert (s. Listing 6).

///usr/bin/env jbang "$0" "$@" ; exit $?
// //DEPS <dependency1> <dependency2>
import static java.lang.System.*;
public class Query {
public static void main(String... args) {
 out.println("Hello World");
}
}
Listing 6: Hello World

Mittels jbang edit Query.java generiert JBang ein temporäres Gradle-Projekt mit den relevanten Dependencies, sodass eine IDE sie auch korrekt auflösen kann. Das Projekt öffnet sich in der IDE (IntelliJ Idea, Eclipse, VS Code) (s. Listing 7). Nach einem Update der Abhängigkeiten kann das unsichtbare Projekt mittels edit wieder aktualisiert werden.

---
jbang edit --open=code Query.java
[jbang] Running `sh -c code /Users/mh/.jbang/cache/projects/Query.java_
jbang_af9d1b3ed59c667238ae61b13a5c64c0d7e4486ac0f3f16fe190e844272620f4/
Query`
/Users/mh/.jbang/cache/projects/Query.java_jbang_
af9d1b3ed59c667238ae61b13a5c64c0d7e4486ac0f3f16fe190e844272620f4/Query
Listing 7: Initialisierung und Bearbeitung des Query.java-Quellcodes

Die aktuelle Skriptdatei wird mittels eines symbolischen Links eingebunden, sodass sie im aktuellen Verzeichnis verbleibt ohne Projekt-Set-up. Das ist auch eine der schönen Eigenschaften von JBang, nur die aktuelle Skriptdatei ist relevant, alle Infrastruktur verschwindet aus dem Sichtfeld.

In dem Beispiel in Listing 8 fragen wir eine Relationale Datenbank ab und stellen das Ergebnis mit einer Ascii-Art-Tabelle dar (s. Listing 9).

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.postgresql:postgresql:42.2.24
//DEPS com.github.freva:ascii-table:1.2.0
import static java.lang.System.*;
import java.sql.*;
import java.util.*;
import com.github.freva.asciitable.*;
public class Query {
public static void main(String... args) throws Exception {
 // JDBC URL aus Umgebungsvariable
 try (var con=DriverManager.getConnection(
 getenv("JDBC_URL"));
 var stmt=con.createStatement();
 // Alle Parameter zu AbfrageString
 var rs=stmt.executeQuery(String.join(" ",args))) {
 var meta=rs.getMetaData();
 // Spaltennamen als Feld
 var cols=new String[meta.getColumnCount()];
 for (int c=1;c<=cols.length;c++)
 cols[c-1]=meta.getColumnName(c);
 int row=0;
 // Werte als zweidimensionales Feld (max 100 Zeilen)
 String[][] rows=new String[100][];
 while (rs.next() || row>=rows.length) {
 rows[row]=new String[cols.length];
 for (int c=1;c<=cols.length;c++)
 rows[row][c-1]=rs.getString(c);
 row++;
 }
 out.println(AsciiTable.getTable(cols,
 Arrays.copyOf(rows,row)));
 }
}
}
Listing 8: Datenbankabfrage mittels JDBC
export JDBC_URL=
"jdbc:postgresql://db-examples.cmlvojdj5cci.us-east-1.rds.amazonaws.com/
northwind?user=n4examples&password=36gdOVABr3Ex"
jbang Query.java "select contact_name, city, country from customers limit
5"
[jbang] Building jar...
+--------------------+-------------+---------+
| contact_name | city | country |
+--------------------+-------------+---------+
| Maria Anders | Berlin | Germany |
+--------------------+-------------+---------+
| Ana Trujillo | México D.F. | Mexico |
+--------------------+-------------+---------+
| Antonio Moreno | México D.F. | Mexico |
+--------------------+-------------+---------+
| Thomas Hardy | London | UK |
+--------------------+-------------+---------+
| Christina Berglund | Luleå | Sweden |
+--------------------+-------------+---------+
Listing 9: Ausgabe der Abfrage einer Postgres Northwind-Datenbank

JBang bringt auch schon einige Quelltext-Vorlagen für spezifische Anwendungen mit, diese können mit --template= oder -t angewandt werden. Listing 10 zeigt ein Beispiel für Kommandozeilenanwendungen mit Picocli. Weitere existierende Vorlagen sind über jbang template list verfügbar:

  • agent = Agent template
  • cli = CLI template
  • hello = Basic Hello World template
  • hello.kt = Basic kotlin Hello World template
  • qcli = Quarkus CLI template
  • qmetrics = Quarkus Metrics template
  • qrest = Quarkus REST template

Man kann aber auch leicht Vorlagen für das eigene Team oder Projekt hinzufügen (s. Listing 11).

Mehrere Java-Dateien und Ressourcen können in JBang auch genutzt werden. Wie gehabt wird per Kommentar angegeben, wo diese zu finden sind beziehungsweise wohin Dateien im aktuellen Verzeichnis innerhalb der Jar-Datei abgelegt werden sollen:

  • Quellcode: SOURCES */.JAVA
  • Ressourcen: FILES META-INF/resources/index.html/code>=index.html

Listing 12 zeigt ein Quarkus-Http-Service-Beispiel. Da Max hauptberuflich an Quarkus arbeitet, gibt es dafür einige dedizierte Konfigurationsoptionen und Templates für Microservices und andere Anwendungen.

// jbang init -t cli Cli.java
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.5.0
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import java.util.concurrent.Callable;
@Command(name = "Cli", mixinStandardHelpOptions = true,
 version = "Cli 0.1", description = "Cli made with jbang")
class Cli implements Callable<Integer> {
@Parameters(index = "0", description = "The greeting to print",
 defaultValue = "World!")
private String greeting;
public static void main(String... args) {
 int exitCode = new CommandLine(new Cli()).execute(args);
 System.exit(exitCode);
}
@Override
public Integer call() throws Exception {
 System.out.println("Hello " + greeting);
 return 0;
}
}
// jbang Cli.java Jbang!
// Hello Jbang!
Listing 10: PicoCLI-Beispiel
jbang template add --name myapp-starter myapp.java logo-banner.jpg app.
properties
Listing 11: Template hinzufügen
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS io.quarkus:quarkus-resteasy:1.8.1.Final
//SOURCES **/*.java
//FILES META-INF/resources/index.html=index.html
import static java.lang.System.*;
import io.quarkus.runtime.*;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.*;
@Path("/hello")
@ApplicationScoped
public class quarkus {
@GET
public String hello() {
 return "Hello Quarkus";
}
}
Listing 12: Quarkus-Http-Service

Ausführung

Da JBang auch URLs als Quelle für die Skripte unterstützt, kann ich mein HTML-Parser-Skript auch zum Beispiel von einem GitHub Gist laden: jbang https://git.io/JasGS https://neo4j.com/developer/ a.page. Damit kann ich meinen Code ohne Kompilierung/Deployment transparent bereitstellen. Es wird aber von JBang nachgefragt, ob der URL vertraut werden soll.

JBang-Skripte können auch als ganz normale Shellskripte ausgeführt werden, dann müssen sie in der ersten Zeile einen Kommentar enthalten, der JBang als Interpreter ausweist: ///usr/bin/ env jbang "$0" "$@"; exit $? JBang kann auch Jar-Dateien direkt ausführen und Skripte für jshell mit dem Suffix .jsh, oder auch Maven-Koordinaten, die auf ein Jar mit einer geeigneten Main-Klasse zeigen.

Häufig wiederkehrende Aufrufe können als Aliase im „Katalog” abgelegt werden, der auch zentral, zum Beispiel auf GitHub, veröffentlicht werden kann (s. Listing 13).

Ein sehr schönes Beispiel von Max ist ein vorpaketierter Minecraft-Server [jbang-minecraft], der die ganze Komplexität auf ein jbang sponge@jbangdev/jbang-minecraft reduziert.

# Ausführen von Maven Koordinaten
jbang -m=org.neo4j.shell.Main org.neo4j:cypher-shell:4.3.6
# Alias hinzufügen
jbang alias add --name=cypher \
 -m=org.neo4j.shell.Main org.neo4j:cypher-shell:4.3.6
# Alias ausführen
jbang cypher -a neo4j+s://demo.neo4jlabs.com \
-d movies -u movies -p movies \
"MATCH () RETURN count(*);"
Listing 13: Ausführen und Aliase, hier mit Neo4js Cypher-Shell

Build und Packaging

JBang hat sich aus der initialen Idee einer Ausführungsumgebung weiterentwickelt und bietet jetzt auch andere Dienste an. Zum Beispiel können damit kleine Projekte gebaut und paketiert werden, sogar komplette Maven Deployments oder Erzeugung von Docker Container Images (s. Tabelle 1).

Tabelle 1: JBang-Kommandos

Andere Anwendungen

Testcontainers
Mit dem Testcontainers-Projekt können Docker Container mittels einer fluent Java API konfiguriert, gestartet und verwaltet werden. Sowohl generische Container für beliebige Anwendungen als auch schon vorkonfigurierte Container für Datenbanken, Webserver usw. sind vorhanden.

Dank JBang kann man jetzt Container und Java-Tests in einer Datei kapseln und diese zum Beispiel zur Reproduktion von Fehlern oder zur Demonstration von Features. Listing 14 zeigt ein Beispiel mit dem Neo4j-Testcontainer, das eine Neo4j-Instanz als Docker Container startet und dann mit dem Java-Treiber eine Verbindung öffnet und eine Abfrage ausführt.

GitHub Actions
Für die Automatisierung von GitHub Actions Continuous Integration Aufgaben hat Max Andersen eine [jbang-github-action] bereitgestellt (s. Listing 15). Das war auch einer der ursprünglichen Treiber für die Entwicklung von JBang – GitHub Actions-Skripte in Java zu implementieren, nicht nur in Java-Script oder Python.

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.testcontainers:neo4j:1.15.3
//DEPS org.neo4j.driver:neo4j-java-driver:4.3.4
import static java.lang.System.*;
import java.util.*;
import org.testcontainers.containers.*;
import org.neo4j.driver.*;
public class Neo4jTest {
private static Neo4jContainer startContainer() {
 var container = new Neo4jContainer()
 .withAdminPassword(null);
 container.start();
 return container;
}
private static Value queryDatabase(String boltUrl) {
 try (
 Driver driver = GraphDatabase.driver(boltUrl,
AuthTokens.none());
 Session session = driver.session()) {
 return session.run("RETURN 1", Map.of()).single().get(0);
 }
}
public static void main(String... args) {
 var container = startContainer();
 var result = queryDatabase(container.getBoltUrl());
 out.println(result);
 assert result.asLong() == 1L;
 container.stop();
}
}
Listing 14: TestContainer starten und nutzen
on: [push]
jobs:
jbang:
runs-on: ubuntu-latest
name: A job to run jbang
steps:
- name: checkout
uses: actions/checkout@v1
- uses: actions/cache@v1
with:
 path: /root/.jbang
 key: ${{ runner.os }}-jbang-${{ hashFiles('*.java') }}
 restore-keys: |
${{ runner.os }}-jbang-
- name: jbang
uses: jbangdev/jbang-action@v0.81.0
with:
 script: createissue.java
 scriptargs: "my world"
 env:
 JBANG_REPO: /root/.jbang/repository
 GITHUB_TOKEN: ${{ secrets.ISSUE_GITHUB_TOKEN }}
Listing 15: GitHub Action mittels JBang

App Store

Viele Räder will man nicht immer wieder neu erfinden, das gilt genauso für Skripte. Daher ist mit JBang auch ein „App Store” für Skripte verfügbar, diese können direkt von der Kommandozeile aufgerufen werden.

Natürlich sollte man sich vergewissern, dass diese Skripte wirklich die Aufgaben erfüllen, die sie vorgeben. Aus Sicherheitsgründen vertraut man eher bekannten Projekten/Autoren. Beispiele:

  • jbang jreleaser@jreleaser – Java-Projekte publizieren von Andres Almiray
  • jfrprint@mbien/JFRLog~cli – Ereignisse aus JFR-Logs auflisten von Michael Bien
  • httpd@quintesse – Http-Server für das aktuelle Verzeichnis von Tako Schotanus
  • tabula@tabulapdf/tabula-java – Tabellen aus PDF extrahieren von Max Rydahl Andersen

Diese Apps aus dem Katalog können mittels jbang app install auch lokal unter einem einprägsamen Namen einmalig installiert und dann einfach wie eine ausführbare Datei verwendet werden.

Mein Kollege Michael Simons hat in einem schönen Beispiel mittels der Java-Bibliothek zur Fernsteuerung von MacOS seine aktuell spielenden Musiktitel erfasst, diese könnte man dann zum Beispiel an eine API weiterreichen [simons-itunes].

Fortgeschrittene Features

JBang hat eine Reihe weiterer Features, auf die ich hier nicht eingegangen bin, die aber im Detail in der eingebauten Hilfe und Onlinedokumentation [jbang-docs] erklärt werden:

  • Aufzeichnung von JFR-Events (--jfr)
  • Java-Debugger aktivieren (--debug)
  • Class-Data-Sharing für schnelleres Start-up (--cds)
  • JDK-Management wie SDKMan,
  • Erzeugung von Java-Agents
  • Offline Modus (--offline)
  • Erzwungene Aktualisierung (--fresh)
  • Management von eines JBang-Wrappers im Projekt ähnlich wie von eines JBang-Wrappers im Projekt ähnlich wie gradlew oder mvnw

Fazit

Während ich ursprünglich nur von der Fähigkeit wusste, Java-Dateien wie Skripte auszuführen, hat mich JBang in seinem Umfang beeindruckt. Max versucht damit, die angenehmen Eigenschaften der Entwickler- und Nutzerfreundlichkeit von Python und Node.js auch für Java bereitzustellen. Dabei beschränkt er sich nicht auf die Ausführung, sondern umfasst auch Kompilierung, Bereitstellung, Deployment und Installation von Anwendungen.

Ich bin rundherum begeistert, es zeigt sich wieder einmal, wenn begeisterte Entwickler ihre eigenen Probleme angehen, kommen gute Lösungen dabei heraus.

Das einzige Manko, das ich sehe, ist die Kompatibilität der erzeugten Artefakte. Obwohl JBang Standard-Java-Tools für seine Arbeit hinter den Kulissen verwendet, stehen die erzeugten Dateien nicht automatisch in einem lokalen Maven-Repository auch für andere Umgebungen bereit und die JBang-Kataloge sind ebenso ein proprietäres Format.

Weitere Informationen

[ascii-table]
https://github.com/freva/ascii-table

[jbang]
https://www.jbang.dev/

[jbang-docs]
https://www.jbang.dev/documentation

[jbang-everywhere]
https://xam.dk/blog/jbang-everywhere/

[jbang-examples]
https://github.com/jbangdev/jbang-examples

[jbang-github-action]
https://github.com/marketplace/actions/java-scripting-w-jbang

[jbang-historie]
https://xam.dk/blog/unleasing-the-scripting-powers-of-java/

[jbang-k8s-cli-java]
https://github.com/jbangdev/k8s-cli-java

[jbang-minecraft]
https://github.com/jbangdev/jbang-minecraft

[jbang-video-jugsaxony]
https://vimeo.com/499180554

[JEP330]
https://openjdk.java.net/jeps/330

[jsoup]
https://github.com/jhy/jsoup

[s imons-itunes]
https://gist.github.com/michael-simons/c2fb92c387b2a7c7300ff686bac88177

. . .

Author Image

Michael Hunger

Teamleiter
Zu Inhalten

Michael Hunger interessiert sich für alle Belange der Softwareentwicklung, denn diese gehört zu seinen großen Leidenschaften. Der Umgang mit den beteiligten Menschen ist ein besonders wichtiger Aspekt. Michael Hunger leitet das Team der Neo4j Labs und unterstützt alle Nutzer der Graphdatenbank tagtäglich bei der erfolgreichen Realisierung ihrer Projekte und Lösung ihrer Fragen und Probleme.


Artikel teilen