Der zehnte Geburtstag

Spring Boot 2.3 – das sind die neuen Features

Michael Simons

© NSC Photography/Shutterstock.com

Mit Spring Boot 2.3 erschien im Mai 2020 die nunmehr zehnte große Spring-Boot-Version, rund sechs Jahre nach Version 1.0. Ich habe das Projekt in diesem Zeitraum sowohl als Bug-Reporter als auch als Contributor, Benutzer und Autor begleitet.

In dieser Zeit hat mich eine Sache immer wieder begeistert: Das Spring-Boot-Team hatte immer ein offenes Ohr für Trends und Bedürfnisse der Benutzerinnen. Oftmals wurde abgewartet und abgewogen, bis eine optimierte Lösung angeboten werden konnte. Für Spring Boot 2.3 ist das in meinen Augen eine erhebliche Verbesserung im Build-Prozess. War das von Spring Boot 2014 favorisierte Fat-Jar-Modell damals im Vergleich zu hochkomplexen WAR und EAR Deployments ein erstrebenswertes Ziel, so hat sich die Welt inzwischen weitergedreht. Spring Boot 2.3 trägt dieser Tatsache Rechnung und startet mit Buildpack-Support und Layered Jars in eine neue Ära.

Bereits in meinem Leitartikel zu Spring Boot 2.2 auf JAXenter im vergangenen Jahr habe ich die neuen Herausforderer im Microservices-Umfeld erwähnt: Micronaut und Quarkus. Diese Frameworks und die Art und Weise, wie mit ihnen extrem schnell startende Services gebaut, nativ kompiliert und komfortabel in Docker Images paketiert werden können, haben Spring Boot 2.3 und das Spring Framework im vergangenen Jahr maßgeblich beeinflusst. Auf die native Kompilierung von Spring-basierten Anwendungen werde ich am Ende des Artikels eingehen. Quarkus hat seit Version 1 ein Dockerfile – sowohl für JVM als auch für Native Images – mitgeliefert. Das Ziel-Deployement war schnell erkennbar. Spring Boot zieht nun nach.

Optimierte Docker Images

Prominentestes 2.3-Feature ist wohl eine Erweiterung der Spring-Boot-Maven- und Gradle-Plug-ins: Beide können jetzt auf Basis von Cloud Native Buildpacks Docker Images erzeugen. Buildpacks sind ein Konzept, das ursprünglich von Heroku stammt. Heroku ist eine „Cloud Plattform as a Service“, die seit jeher unterschiedlichste Programmiersprachen und Plattformen unterstützt und großen Bedarf hat, Anwendungen mit optimierten Images möglichst schnell und optimal zu starten. Buildpacks sollen die operative Last von Entwicklern nehmen und gleichzeitig Compliance- und Securityrichtlinien sicherstellen.

Für Spring Boot 2.3 können Buildpacks – eine Docker Installation vorausgesetzt – mit ./mvnw spring-boot:build-image genutzt werden. Es entsteht ein auf Ubuntu 18 LTS basierendes Image inklusive korrekter Java-Heap-Settings (für einen Betrieb im Container), dem JVMKill Agent (der die VM nach einem OutOfMemoryError beendet) und einiger Dinge mehr. Der Agent läuft außerhalb der JVM und verhindert, dass die JVM in einem undefinierten Zustand nach OutOfMemoryError weiterläuft.

Kodifizierung von Best Practices

Es gibt gute Gründe, keine Buildpacks zu benutzen (zum Beispiel aufgrund des bestehenden Toolings oder dem Wunsch, eigene Base Images zu nutzen). Dabei existiert mit dem Default-Deployment von Spring-Boot-Anwendungen, dem „Fat Jar“-Model, jedoch ein Problem: Die gesamte Anwendung, alle Libraries und Ressourcen liegen als ein Artefakt vor. Was in einem Szenario von Vorteil ist, ist ein riesiger Nachteil, wenn dieses Artefakt in einem Container-Image weiterverteilt und nicht per java -jar auf einem beliebigen Server gestartet wird.

Spring Boots Fat Jars sind eine komfortable Art, komplette Anwendungen inklusive aller Abhängigkeiten und der Runtime zu paketieren, aber gerade in Szenarien, in denen Services wegen kleiner Änderungen neu verteilt werden, nicht optimal: Es muss jedes Mal ein großes Jar mitsamt allen nicht geänderten Abhängigkeiten neu verteilt werden und dementsprechend ein neuer, großer Layer im Container-Build.
So wie Spring Boot seit über fünf Jahren versucht, Spring Best Practices mit Startern und automatischer Konfiguration zu kodifizieren, so geht es diesen Weg nun auch bezüglich Docker Images, um das oben beschriebene Problem der Fat-Jars zu lösen. Spring Boot nutzt dazu ein neues Fat-Jar-Layout sowie einen eigenen Boot Loader, dessen Optionen ich bereits 2018 im Spring-Boot-Buch beschrieben habe. Dieser Mechanismus wird nun um Layered Jars ergänzt. Die Defaultlayer sehen aus wie in Listing 1 gezeigt:

  • dependencies für alle Nicht-Snapshot-Dependencies
  • snapshot-dependencies für Snapshot-Dependecies
  • resources für statische Ressourcen wie META-INF/resources/, resources/, static/, public/.
  • application für die eigentliche Anwendung
- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

Die Reihenfolge hier ist wichtig: Dependencies ohne Snapshot werden selten geändert. Sie liegen unten, darüber Dependencies, die sich öfter ändern können, dann die Ressourcen und schlussendlich die Anwendung.

Ein Layered Jar wird über die Konfiguration des Maven- bzw. Gradle-Plug-ins erstellt. Listing 2 zeigt dies für Maven, das nachfolgende Beispiel (Layered Jars mit dem Spring-Boot-Gradle-Plug-in) für Gradle.

<project>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.3.0.RC1</version>
        <configuration>
          <layers>
            <enabled>true</enabled>
          </layers>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
bootJar {
  layered()
}

Die Layer einer so paketierten Anwendung können sogar mit einem eingebauten Befehl angezeigt werden:

java -Djarmode=layertools -jar target/demo-0.0.1-SNAPSHOT.jar list
dependencies
spring-boot-loader
application

Mit extract statt list können die Layer entpackt werden. Im Vergleich zum generischen Google-Containertool jib kennt das Jar-File seine eigenen Layer und ermöglicht dieses Tooling. Darauf aufbauend kann relativ schnell ein Docker Image mit korrespondierenden Image-Layern erzeugt werden. Der Layer der Abhängigkeiten ändert sich dann entsprechend selten, Deployments sind schneller, da nur noch ein kleiner Teil des Image neu erzeugt werden muss, und die neuen Images sind entsprechend kleiner.

Welche Java-Version?

Die gleiche Frage wie letztes Jahr: Welche Java-Version wird unterstützt? Während andere Frameworks es sich recht leicht erlauben können, den Support für Java 8 abzuschalten (Quarkus wird dies mit 1.6 tun und fortan Java 11 erfordern), ist das für Spring Boot und insbesondere das zugrunde liegende Spring Framework schwierig, ist doch die Liste von Nutzern groß, die nicht ohne Weiteres auf Java 11+ wechseln können. Spring Boot 2.3 setzt wie gehabt auf Spring Framework 5.2, das bis Ende 2021 unterstützt werden wird, und ist aktuell Java-8- bis Java-15-kompatibel. Dementsprechend kann Spring Boot 2.3 bereits mit Java 14 genutzt werden. Mit den neuesten Spring-Data-Modulen – allen voran Spring Data Neo4jRX – können sogar JDK 14 Records (Preview-Feature von unveränderlichen Data Classes) genutzt werden, um Datenbankzugriff zu realisieren.

Erwähnenswerte Features und Änderungen

R2DBC-Support: Wenn R2DBC (Reactive Relational Database Connectivity) auf dem Klassenpfad ist, wird automatisch eine Connection Factory – ähnlich wie eine JDBC DataSource – konfiguriert. Gleiches gilt für Spring Data R2DBC.

Verbesserungen des Health Actuator, bessere Health-Indikatoren für Datenbanken: Für SQL-Datenbanken wird nun java.sql.Connection#isValid anstelle einer Abfrage genutzt – es sei denn, eine solche ist definiert. Ähnliches wird für Neo4j getan. Der Neo4j Health Endpoint zeigt nun zusätzlich die Version des Servers und die Edition an.

Lebendig und bereit? Spring Boot kann nun unterscheiden, ob eine Anwendung lediglich „alive“ oder auch in der Lage ist, Anfragen zu beantworten: Das Setzen von management.health.probes.enabled auf true konfiguriert den Health Endpoint so, dass er unter /actuator/health/liveness Auskunft über generelle Verfügbarkeit gibt und unter /actuator/health/readiness darüber, ob Anfragen beantwortet werden können.

Web Starter ohne Validation: Beide Web Starter – imperativ und reaktiv – kommen nun ohne die Abhängigkeit zum Validation Starter aus. Dies reduziert die Größe von einfachen Webanwendungen. Wird Java Bean Validation benötigt, muss nun spring-boot-starter-validation als separate Abhängigkeit aufgeführt werden.

Graceful Shutdown: Für alle unterstützten Webserver sowie für die reaktiven Konnektoren wird nun ein Graceful Shutdown unterstützt. Durch server.shutdown.grace-period kann ein Zeitraum definiert werden, in dem die Anwendung keine neuen Anfragen mehr entgegennimmt und nur noch offene Anfragen abarbeitet.

Der Spring Cloud Connectors Starter wurde entfernt: Nach einer Deprecation in Spring Boot 2.2 wurde nun der Spring Cloud Connectors Starter entfernt. Er wird langfristig durch java-cfenv ersetzt, einer neuen Library zum Zugriff auf Cloud Foundry Services.

DynamicPropertySource: Die Annotation DynasmicPropertySource wurde zwar bereits mit Spring Boot 2.2.6 eingeführt, ich halte sie dennoch für erwähnenswert. Sie vereinfacht die Arbeit mit Testdatenbanken und anderen dynamischen Quellen für Konfiguration ungemein:

@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
  registry.add("org.neo4j.driver.uri", embeddedDatabaseServer::boltURI);
  registry.add("org.neo4j.driver.authentication.password", () -> "");
}

Mehr ist nicht mehr nötig, um die Umgebung eines @SpringBootTest aus einer statischen Init-Methode zu beeinflussen.

Bean Overriding während Tests: Im Applikationskontext werden Beans mit ihrem Namen identifiziert. Wird eine zweite Bean gleichen Namens definiert, überschreibt sie die erste. Das führt in vielen Fällen zu Problemen und unerwartetem Verhalten und wurde daher bereits in Spring Boot 2.1 für den Kontext der SpringApplication abgeschaltet. 2.3 tut dies nun auch für den ApplicationContextRunner, der während Spring-Boot-Tests genutzt wird. Um das vorherige Verhalten in Tests wiederherzustellen, kann die Builder-Methode withAllowBeanDefinitionOverriding genutzt werden.

Open Session In View (OSIV): Das Open-Session-In-View-Pattern (OSIV) wird von vielen Experten der Datenbankentwicklung als kritisch angesehen. Spring Boot loggt seit geraumer Zeit eine Warnung, wenn es für JPA/Hibernate aktiviert ist, für Neo4j-OGM wurde es nun in Absprache mit dem Neo4j-OGM-Team per Default deaktiviert.

Konfiguration

Die Eigenschaften, die Spring Boot über Properties, Environment-Variablen und andere Mechanismen zur Konfiguration zur Verfügung stellt, hängen natürlich direkt von den unterstützten Libraries ab und sind daher sehr umfangreich. Einige davon wurden von Spring Boot 2.2 zu Spring Boot 2.3 deprecated und einige ersetzt. Das Spring Boot 2.3.0 RC1 Configuration Changelog bietet eine gute Übersicht. Erwähnenswert ist hier keine spezielle Eigenschaft, sondern ein neuer Wert iso, der für verschiedene Datums- und Datum-Zeit-Angaben verwendet werden kann und das entsprechende ISO-8601-Format des jeweiligen Typens konfiguriert. Verwendet werden kann iso unter anderem mit:

  • spring.mvc.format.date
  • spring.mvc.format.date-time
  • spring.mvc.format.time
  • spring.webflux.format.date
  • spring.webflux.format.date-time
  • spring.webflux.format.time

Libraries

Auch mit Spring Boot 2.3 ist die Liste der verwalteten Abhängigkeiten und die dazugehörige Auto Configuration nicht kleiner geworden. Es ist nachvollziehbar, dass viele Entwickler bei einem ersten Blick auf Spring Boot denken: „Das ist aber komplex, die Fülle an Optionen ist einschüchternd.“ Man sollte aber nicht vergessen, dass Spring Boot enorm erfolgreich ist, weil es viele Abhängigkeiten im Kontext des Spring-Ökosystems konfiguriert und umkehrt viele Hersteller möchten, dass ihr Produkt möglichst gut in der Plattform funktioniert und sie ihre Libraries integriert wissen wollen. Oft genutzte und aktualisierte Abhängigkeiten sind unter anderem:

  • Couchbase Client
  • Elasticsearch
  • Flyway
  • JUnit
  • Kotlin
  • Lettuce
  • MongoDB
  • Micrometer
  • Neo4j-OGM

Die Liste aktualisierter Spring-eigener Module ist ähnlich groß:

  • Spring Data Neumann
  • Spring Framework
  • Spring HATEOAS
  • Spring Integration
  • Spring Kafka
  • Spring Security
  • Spring Session

In den meisten Fällen hängt Boot von diesen Projekten hinsichtlich Auto Configuration und Ähnlichem ab, teilweise bestehen aber auch umgekehrte Abhängigkeiten, meist über das Spring Framework selbst.

Erwähnenswert ist an dieser Stelle die Aktualisierung vieler Datenbankclients. In den Treibern für Neo4j, MongoDB und Cassandra gibt es viele Änderungen, die teilweise Breaking Changes sind. Eine Abstraktion in Form von Spring Data schützt die meisten Projekte massiv vor diesen Auswirkungen. Der Nutzen von Spring Data geht weit über die reine Repository- beziehungsweise Mapping-Abstraktion hinaus.

Übersicht der verfügbaren Changelogs

Grundlage für den Artikel sind – abgesehen von eigener Arbeit – die öffentlich verfügbaren Changelogs. Ich kann nur empfehlen, die entsprechenden Wiki Pages auf GitHub zu verfolgen, um für ein Upgrade gewappnet zu sein. Ebenso möchte ich die Empfehlung des Spring-Teams wiederholen: Ein Upgrade sollte in Phasen erfolgen. Falls die Anwendung noch auf Spring Boot 2.0 basiert, sollte schrittweise nach 2.3 migriert werden. Den größten Aufwand sehe ich im ersten Schritt (von 2.1 auf 2.3).

Ausblick: Going native

2019 war es in der Java-Welt fast unmöglich, um das Thema native Kompilierung mit der GraalVM herumzukommen. Die eingangs erwähnten Frameworks Quarkus und Micronaut waren Vorreiter in Bezug auf dieses Thema. Der Autor selbst war damit beschäftigt, den Neo4j-Datenbanktreiber dahingehend zu ertüchtigen, dass er nativ mit der GraalVM kompilierbar ist. Mit dem experimentellen „Spring Graal Native“-Projekt arbeitet das Spring-Team daran, native Kompilierung von Spring-Boot-Anwendungen zu ermöglichen. Dabei müssen unter anderem folgende Herausforderungen gelöst werden:

  • Dynamische Konfiguration von Anwendungen – der extrem flexible Konfigurationsmechanismus von Spring Boot ist an dieser Stelle Fluch und Segen zugleich.
  • Dynamisches Laden von Klassen und Codepfaden.
  • GraalVM Substitutions für Teile von Spring und Spring Boot, die aktuell nicht nativ kompilierbar sind.

Aktuell ist noch viel Handarbeit nötig, um selbst kleine Spring-Boot-Anwendungen nativ zu kompilieren, wie Jonas Hecht in seinem Post Anfang Mai eindrucksvoll beschrieben hat. Es ist aber zu erwarten, dass das Spring-Team auch diese Hürden nehmen wird.

Geschrieben von
Michael Simons
Michael Simons
Michael ist Vater, Ehemann, Radfahrer und Java Champion. Er ist Autor des ersten deutschen Buchs über Spring Boot 2 und Spring 5. Michael ist Software Engineer bei Neo4j. Als Mitgründer und Leiter der EuregJUG in Aachen ist Michael seit langem in der Community-Arbeit engagiert.
Kommentare

Hinterlasse einen Kommentar

avatar
4000
  Subscribe  
Benachrichtige mich zu: