TL;DR

Manchmal ist eine Kommandozeile das beste Userinterface. Wie Ihr mithilfe von Spring Shell ganz einfach Kommandozeilenanwendungen entwicklen könnt, erfahrt ihr unten.

Für die Ungeduldigen gehts hier direkt zum Code.

Spring Shell

Mit Spring Boot lassen sich jetzt auch einfach Kommandozeilenanwendungen entwickeln. Möglich wird das durch die in Version 2 hinzugekommene Integration des Spring Shell Projekts.

Spring Shell basiert auf dem JLine Framework, welches weiterhin im Hintergrund für die Interaktion mit der Kommandozeile verantwortlich ist. Dank der Arbeit der Spring Entwickler, muss man sich bei der täglichen Arbeit allerdings keine Gedanken über das Parsen von Benutzereingaben machen. Diese Arbeit übernimmt das Framework.

 

Projektsetup

Um Spring Shell verwenden zu können, benötigen wir ein ganz gewöhnliches Spring Boot Projekt. Dafür müssen wir als erstes die „Bill Of Materials“ (BOM) für Spring Boot in unsere Projekt importieren.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.3.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Außerdem benötigen wir Abhängigkeiten zu spring-boot-starter und spring-shell-starter.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.shell</groupId>
        <artifactId>spring-shell-starter</artifactId>
        <version>2.0.1.RELEASE</version>
    </dependency>
</dependencies>

Um am Ende eine einzelne JAR mit allen benötigten Libraries zu erhalten, sollten wir auch das Spring Boot Maven Plugin konfigurieren.

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.1.3.RELEASE</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Jetzt brauchen wir, wie bei Spring Boot Anwendungen üblich, nur noch unsere Main-Klasse.

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

Wenn wir unsere Anwendung jetzt starten, sehen wir das Spring auf eine Eingabe wartet:

shell:>

Führen wir nun das Kommando help aus, erhalten wir einen Überblick über alle verfügbaren Befehle. Vorerst werden hier nur die Standardbefehle von Spring Shell angezeigt. Das werden wir allerdings im nächsten Schritt ändern.

shell:>help
AVAILABLE COMMANDS

Built-In Commands
    clear: Clear the shell screen.
    exit, quit: Exit the shell.
    help: Display help about available commands.
    history: Display or save the history of previously run commands
    script: Read and execute commands from a file.
    stacktrace: Display the full stacktrace of the last error.

 

Eigene Befehle implementieren

Jeder Befehl in einer Spring Shell Anwendung ist einer Methode in unserem Code zugeordnet. Beim Start der Anwendung durchsucht Spring den Code nach passenden Klassen, welche mithilfe von Annotationen markiert seien müssen.

Mit der Annotation @ShellComponent teilen wir Spring mit, dass diese Klasse Befehle für die Kommandozeile enthält. Gleichzeitig sorgt sie dafür, dass unsere Klasse im Dependency Injection Container registriert wird.

Alle Methoden die als Befehle in der Kommandozeile verfügbar sein sollen, sind mit der Annotation @ShellMethod markiert. Die Annotation erlaubt uns auch gleich eine Beschreibung für den Befehl anzugeben, welche später auf der Hilfe Seite angezeigt wird. Außerdem können wir unsere Befehle hier mit dem group Parameter zu Gruppen zusammenfassen. Standardmässig werden die Befehle nach dem Klassennamen gruppiert.

@ShellComponent
public class CalculatorCommands {

    @ShellMethod(value = "Add two integers together.", group = "Rechner")
    public int add(int left, int right) {
        return left + right;
    }

    @ShellMethod(value = "Subtracts on integer from another.", group = "Rechner")
    public int subtract(int left, int right) {
        return left - right;
    }
}

 

Wenn wir unsere Anwendung jetzt erneut starten und die Hilfe öffnen, sehen wir die zwei neuen Befehle inklusive Beschreibung:

shell:>help
AVAILABLE COMMANDS

...

Rechner
    add: Add two integers together.
    subtract: Subtracts on integer from another.

 

Beim Ausführen unserer Befehlt müssen die Parameter entweder in der selben Reihenfolge wie in der Methodensignatur angeben werden, oder die Namen der Parameter müssen explizit angegeben werden:

shell:>add 1 2
3

shell:>add --b 1 --a 2
3

Tip: Um das Ergebnis darzustellen ruft Spring automatisch die toString() Methode auf dem Objekt auf, welches von unserer Methode zurückgegeben wird. Deshalb muss bei komplexen Objekten darauf geachtet werden, dass diese Methode entsprechend überschrieben wird, da ansonsten nur der Name der Klasse ausgegeben wird.

 

Erweiterung der Hilfe

Wir haben ja bereits gesehen, dass Spring automatisch eine Übersicht über alle verfügbaren Befehle generiert. Allerdings kann das Framework sogar noch mehr. Wenn wir dem help Kommando den Namen eines anderen Befehls übergeben, wird eine detaillierte Hilfe für diesen Befehl angezeigt.

shell:>help add


NAME
    add - Add two integers together.

SYNOPSYS
    add [--left] int  [--right] int 

OPTIONS
    --left  int

        [Mandatory]

    --right  int

        [Mandatory]

 

Diese Hilfe lässt sich noch weiter verbessern, indem wir noch ein Beschreibung für unsere Parameter angeben. Dafür markieren wir diesen mit der Annotation @ShellOption und spezifizieren den help Parameter.

@ShellMethod(value = "Add two integers together.", group = "Rechner")
public int add(
        @ShellOption(help = "The first number.") int left,
        @ShellOption(help = "The second number.") int right) {

    return left + right;
}

Wenn wir jetzt die Hilfe aufrufen, erhalten wir zu jedem Parameter die entsprechende Beschreibung.

shell:>help add


NAME
    add - Add two integers together.

SYNOPSYS
    add [--left] int  [--right] int 

OPTIONS
    --left  int
        The first number.
        [Mandatory]

    --right  int
        The second number.
        [Mandatory]

 

Damit haben wir eine minimale Kommandozeilenanwendungen erstellt. Wenn wir jetzt eine mit mvn package eine JAR Datei erzeugen, können wir unsere Anwendung folgendermaßen starten:

java -jar spring-boot-shell-1.0.0.jar

 

Natürlich war das jetzt ein sehr einfaches Bespiel, allerdings zeigt es den grundsätzlichen Aufbau einer Spring Shell Anwendung. Um einen Überblick über alle Möglichkeiten des Frameworks zu erhalten, lohnt es sich einen Blick in die offizielle Dokumentation zu werfen: Spring Shell Dokumentation

Das komplette Beispiel gibt’s auch als fertiges Projekt zum auschecken und starten: Bitbucket