# 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 (@BeanMethoden)@ComponentScan: aktiviert die Suche nach weiteren@Configurationund@ComponentKlassen, 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
bootRunTask im Projektverzeichnis
Man legt also folgende Struktur an:
- Projektverzeichnis (enthält u. a.
build.gradle.ktsundsrc/)config/.gitignoreapplication-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}" }