# Application

Eine Application ist eines der zentralen Elemente von Spring. Früher lief diese klassisch in einem Servlet Container wie z. B. Tomcat. Dank Spring Boot ist dies nicht mehr nötig, sondern Applications können nun standalone laufen – dies sogar zusammengepackt in einer einzige JAR Datei, inklusive allen Abhängigkeiten. Ein entsprechender Embedded Server wird bei Bedarf von Spring Boot mitgeliefert (z. B. Netty). Falls keine Web-Funktionalitäten benötigt werden, kann auch eine Kommandozeilen-Applikation erstellt werden.

# Start

Einstiegspunkt ist die Standard Java main Methode:

fun main(args: Array<String>) {
  runApplication<DemoApplication>(*args)
}

Hier wird die Spring (Boot) Maschinerie in Gang gesetzt. Es wird die Application-Klasse spezifiziert und eventuelle Kommandozeilenargumente durchgereicht.

# Application Klasse

Die angegebene Klasse ist der erste Anlaufpunkt für Spring, um zu erfahren, was getan werden soll:

@SpringBootApplication
class DemoApplication

Diese Klasse besteht am Anfang aus nicht viel. Einzig allein @SpringBootApplication ist vorhanden. Dies ist eine zusammengefasste Annotation und beinhaltet u. a. die folgenden anderen Annotationen (mehr Details dazu folgen auf der nächsten Seite):

  • @Configuration: markiert die Klasse als Lieferant von Beans (@Bean Methoden)
  • @ComponentScan: aktiviert die Suche nach weiteren @Configuration und @Component Klassen, ausgehend vom Package der Application
  • @EnableAutoConfiguration: aktiviert das automatische Registrieren von bestimmten Standard-Beans und -Konfigurationen durch Spring Boot

Die @SpringBootApplication Markierung dient außerdem zum Auffinden der Application in Tests, da dort ja die main Methode nicht aufgerufen wird, Spring aber trotzdem die Konfigurationen und Abhängigkeiten der Application kennen muss. Spring bringt übrigens eine sehr gute Unterstützung für Unit- und Integration-Tests mit.

Je nachdem welche Komponenten und Konfigurationen gefunden werden, startet Spring z. B. einen Webserver oder eine simple Konsolenanwendung.

# Konfiguration, Application Properties

Der Aspekt der externen Konfiguration ist in Spring stark vertreten. Einstellungen können in einer Datei namens application.properties (im Java Properties Format) abgelegt werden. Diese Datei wird zuerst im Classpath (also in der ausgelieferten JAR Datei) gesucht, danach im aktuellen Verzeichnis. Das macht es möglich Standardwerte auszuliefern, die dann beim Betrieb der Anwendung überschrieben werden können.

Das Format sieht so aus:

my.app.page-title=Hello
my.app.retries=10
my.app.silent=true

my.app.default-author.name=Default Author
my.app.default-author.mail=john@doe.default

TIP

Die Konfigurationsdatei darf auch im YAML-Format sein, dann muss sie application.yml heißen. Das Beispiel von oben sieht in YAML so aus:

my:
  app:
    page-title: Hello
    retries: 10
    silent: true

    default-author:
      name: Default Author
      mail: john@doe.default

Diese Dateien sind aber nicht die einzige Quelle, aus der Spring die Anwendungs-Konfiguration liest. Es wird u. a. auch in Umgebungsvariablen, Java System Properties und Kommandozeilenargumenten geschaut. Alle Details dazu stehen in der Spring Boot Dokumentation (opens new window).

Die konfigurierten Werte können dann auf unterschiedliche Arten ausgelesen werden. Am besten eignet sich das typsichere Mappen der Werte auf Klassen, wie im nächsten Abschnitt beschrieben.

# Type-safe Configuration

Konfigurationseinträgen der Anwendung ist normalerweise ein Präfix vorangestellt, damit es keine Überschneidungen mit anderen Konfigurationseinträgen gibt. Im Beispiel oben war dies "my.app".

Für die entsprechenden Konfigurationen werden eine oder mehrere Klassen angelegt. In Kotlin bietet sich dafür eine (oder mehrere) data class mit val Constructor-Properties an (so können die Werte zur Laufzeit nicht unabsichtlich geändert werden). Für das obige Beispiel könnte das so aussehen:

@ConfigurationProperties("my.app")
@ConstructorBinding
data class MyAppProperties(
  /**
   * Some Documentation.
   */
  val pageTitle: String?,
  val retries: Int = 3,
  val silent: Boolean = false,
  val defaultAuthor: Author?
) {
  @ConstructorBinding
  data class Author(
    val name: String,
    val mail: String
  )
}

Damit Spring weiß, welcher Teil der Konfiguration gemappt werden soll, wird mittels der @ConfigurationProperties Annotation der Präfix angegeben, unter der die Einträge zu finden sind. Die Propertynamen im Konstruktor entsprechen dabei denen in der Konfigurationsdatei. Es wird lediglich vom Bindestrichformat der Datei in das Camelcaseformat der Klasse umgewandelt - page-title in der Datei wird also zu pageTitle in der Klasse. Damit Spring die Werte korrekt zuweisen kann, darf die @ConstructorBinding Annotation nicht fehlen. Wie man sieht können auch Unterelemente strukturiert definiert werden.

Standardmäßig ist diese Funktionalität nicht aktiviert. Man muss aber nur die Applikations-Klasse um @ConfigurationPropertiesScan erweitern, um dies zu tun:

@SpringBootApplication
@ConfigurationPropertiesScan
class DemoApplication

Alle Werte, die nicht als nullable im Code definiert sind, müssen zur Laufzeit in der Konfiguration vorhanden sein, ansonsten wird ein Fehler ausgelöst und die Anwendung startet nicht. Für solche Properties können auch Standardwerte definiert werden (siehe retries und silent im Beispiel). defaultAuthor ist nullable – Spring würde also null übergeben, falls my.app.default-author nicht definiert ist. Wurde my.app.default-author aber definiert, so müssen zwingend name und mail darunter auch definiert werden (da diese beiden Properties im Code in der Author Klasse nicht als nullable definiert sind).

Diese typsichere Konfiguration steht dann als normales Bean zu Verfügung, kann also z. B. in der Application-Klasse angefragt werden:

@SpringBootApplication
@ConfigurationPropertiesScan
class DemoApplication(
  private val config: MyAppProperties
)

# Metadaten

Passend zur Definition im Code kann Spring automatisch Metadaten im JSON Format erzeugen, die von manchen IDEs dann wieder ausgelesen werden. Damit ist dann Syntax-Unterstützung beim Editieren der Konfigurationsdateien möglich. Es kann dann beispielsweise direkt zur Code-Definition gesprungen sowie mögliche Werte und Dokumentation angezeigt werden.

Das Erzeugen der JSON-Metadatendatei übernimmt Gradle automatisch mit dem Kotlin-Annotation-Processor kapt. Dazu müssen in der build.gradle.kts nur zwei Änderungen gemacht werden:

plugins {
  // ...
  kotlin("kapt") version "1.4.30"
}

dependencies {
  // ...
  kapt("org.springframework.boot:spring-boot-configuration-processor")
}

Hiermit werden dann bei jedem Build (z. B. mit dem Gradle Task bootJar) automatisch die Metadaten generiert.

# Entwicklung

Meistens möchte man während der Entwicklung lokal andere Konfigurationen setzen, diese aber nicht im Git oder der von Gradle erstellten .jar Datei hinterlegen, falls beispielsweise Secrets enthalten sind. Dank zweier Umstände kann man im Projekt eine Konfigurationsdatei anlegen, sodass diese nur für die lokale Entwicklung benutzt wird und nirgendwo anders auftaucht:

  • Spring sucht auch nach config/application.properties
  • Gradle startet die Anwendung beim bootRun Task im Projektverzeichnis

Man legt also folgende Struktur an:

  • Projektverzeichnis (enthält u. a. build.gradle.kts und src/)
    • config/
      • .gitignore
      • application-example.properties

Die .gitignore Datei dient dazu, dass die erstellte Konfiguration nicht im Git landet und hat folgenden inhalt:

*
!.gitignore
!application-example.properties

In application-example.properties schreibt man in einer Zeile, was zu tun ist, um eine Entwicklungskonfiguration zu benutzen und gibt noch ein paar Beispiele an:

# create a copy named application.properties and configure for local development

#spring.datasource.url=jdbc:mariadb://host:3306/db
#spring.datasource.username=user
#spring.datasource.password=pass

#spring.jpa.show-sql=true

#spring.security.user.name=admin
#spring.security.user.password=admin
#spring.security.user.roles=admin

#logging.level.root=TRACE

# Logging

Spring Boot konfiguriert automatisch log4j und logback. Man kann sich ganz normal einen log4j Logger holen und losloggen! Die Log-Level können in der Konfiguration definiert werden, also z.B. in application.properties:

# standardmäßig nur ab WARN loggen
logging.level.root=warn

# bestimmte Packages mit DEBUG loggen
logging.level.com.example=debug

TIP

Um einen Logger zu erzeugen empfehle ich kotlin-logging (opens new window). Dafür einfach in build.gradle.kts den dependencies Block um folgendes erweitern:

// https://mvnrepository.com/artifact/io.github.microutils/kotlin-logging-jvm
implementation("io.github.microutils:kotlin-logging-jvm:2.0.4")

So kann man einfach in jeder Kotlin-Datei innerhalb von Klassen (oder auch direkt nach den Imports, falls der Logger für die gesamte Datei verfügbar sein soll) die folgende Zeile ergänzen und hat Zugriff auf einen passend benannten Logger:

private val logger = KotlinLogging.logger {}

# Konsolenanwendung

Eine der einfachsten Anwendungen in Spring ist eine Konsolenanwendung, die einfach startet, irgendwas tut, dabei Text auf der Konsole ausgibt und sich dann direkt wieder beendet.

Dafür muss man nur ein Bean vom Typ CommandLineRunner bereitstellen (der Name ist egal):

  @Bean
  fun runner() = CommandLineRunner { args ->
    /* ... */
  }

In args werden die Kommandozeilenargumente als String-Array übergeben.

TIP

Um komplexere Argumente wie --foo=bar etc. auszuwerten, kann anstatt CommandLineRunner auch ApplicationRunner benutzt werden. Dann wird für args kein String-Array, sondern eine Instanz vom Typ ApplicationArguments übergeben. Damit hat man Zugriff auf verschiedene Methoden (opens new window), um die übergebenen Argumente abzufragen.

In Kombination mit den Themen Konfiguration und Logging könnte eine minimale Anwendungsklasse so aussehen (main Funktion, MyAppProperties Klasse und logger wie oben):

@SpringBootApplication
@ConfigurationPropertiesScan
class DemoApplication(
  private val config: MyAppProperties
) {
  @Bean
  fun runner() = CommandLineRunner { args ->
    logger.info { "starting..." }

    println("pageTitle: ${config.pageTitle}")
    println("args: ${args.toList()}")

    logger.info { "exiting..." }
  }
}

Der Gradle Task build (oder auch nur bootJar) erzeugt eine ausführbare JAR Datei, die alle Dependencies und Ressourcen enthält. Startet man diese dann mit dann so

java -jar ./demo-0.0.1-SNAPSHOT.jar some arguments

erzeugt das die folgende Ausgabe:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.2)

2021-01-25 14:26:14.583  INFO 7088 --- [           main] com.example.demo.DemoApplicationKt       : Starting DemoApplicationKt using Java 11.0.10 on DESKTOP-PC with PID 7088 ...
2021-01-25 14:26:14.588  INFO 7088 --- [           main] com.example.demo.DemoApplicationKt       : No active profile set, falling back to default profiles: default
2021-01-25 14:26:15.714  INFO 7088 --- [           main] com.example.demo.DemoApplicationKt       : Started DemoApplicationKt in 1.816 seconds (JVM running for 2.344)
2021-01-25 14:26:15.715  INFO 7088 --- [           main] com.example.demo.DemoApplication         : starting...
pageTitle: Hello
args: [some, arguments]
2021-01-25 14:26:15.716  INFO 7088 --- [           main] com.example.demo.DemoApplication         : exiting...

TIP

Den Spring-ASCII-Banner kann man mit folgender Konfiguration deaktivieren:

spring.main.banner-mode=off

# Build Info

Wenn man der build.gradle.kts folgende Konfiguration hinzufügt:

springBoot {
  buildInfo()
}

hat man zur Laufzeit Zugriff auf BuildProperties, in denen man u. a. die eigene Version und den Build-Zeitpunkt auslesen kann. Dazu fordert man ein Bean von diesem Typ an:

private val buildInfo: BuildProperties

und hat dann Zugriff auf die Eigenschaften:

logger.info { "starting version ${buildInfo.version} built at ${buildInfo.time}" }