Spring Framework 6 verarbeitet Native Images und baut auf Jakarta EE 9 oder 10

Spring setzt in der neuen Version 6 Java 17 voraus und wechselt zu Jakarta EE 9+. Außerdem bietet es Support für Native Image.

In Pocket speichern vorlesen Druckansicht 12 Kommentare lesen
Lesezeit: 9 Min.
Von
  • Michael Simons
Inhaltsverzeichnis

Zwanzig Jahre nach dem ersten Release des Spring Framework ist nun die sechste Hauptversion erschienen. Ende November soll Spring Boot 3 folgen. Da Letzteres mittlerweile synonym mit Spring verwendet wird, sind Änderungen und Neuerungen gemeinsam zu betrachten.

Um Spring-6-Anwendungen zu kompilieren, braucht es jetzt Java 17. Jürgen Höller, Entwicklungsleiter und Mitgründer von Spring Framework, hatte dies bereits im September 2021 zur SpringOne angekündigt. Was damals noch ambitioniert klang (Java 17 LTS wurde ebenfalls im September 2021 veröffentlicht), ist vor dem Hintergrund dynamischer Java-Releases – aktuell ist Java 19 – eine vernünftige und notwendige Entscheidung. Die letzte Spring-Version, die eine neue Java-Version voraussetzte, war Spring 5 und erschien 2017 – drei Jahre nach Veröffentlichung von Java 8.

Die Probleme von Bibliotheken und Build-Tools mit Java-Versionen jenseits 8 sind mittlerweile lange gelöst, Probleme mit dem Modulsystem oder den stärkeren Einschränkungen unsicherer Klassen wie sun.misc.Unsafe haben nur noch wenige Auswirkungen: Nicht nur das Ausführen von mit Java 8 kompilierten Anwendungen auf Java 17 gelingt problemlos, sondern auch das Kompilieren älteren Sourcecodes.

Sprechen dennoch Gründe dafür, bei Java 8 zu bleiben, ist das möglich: Spring Framework 5.3 wird bis 2024 Open-Source- und bis 2026 kommerziellen Support erhalten. Auch wenn Spring Framework 5.3 und das korrespondierende Spring Boot 2.7 tadellos auf JDK 11 und 17 funktionieren, gibt es wenige Gründe, Java 8 noch produktiv einzusetzen. Meist sind sie politischer oder kommerzieller Natur, etwa wenn Unternehmen große Application-Server verwenden.

Dass Java EE seit 2019 Jakarta EE heißt, ist die Geschichte des Scheiterns der Verhandlungen zwischen Oracle und der Eclipse Foundation nach erfolgreicher Migration der Java-EE-Referenzimplementierungen zu Eclipse GlassFish 5.1.0: Die Marke Java und javax.* -Packages dürfen nicht weiter genutzt werden. Betroffen sind die Java Persistence API (JPA), Java Servlets, Java Message Service (JMS) und andere. Die Weiterentwicklung jeglicher API ist nur außerhalb des javax-Package-Präfix erlaubt. Oracle und die Eclipse Foundation einigten sich auf jakarta: aus javax.servlet wird jakarta.servlet.

In der eigenen Applikation ist der Package-Name schnell mit Suchen und Ersetzen ausgetauscht, aber es gilt auch, alle genutzten Libraries zu aktualisieren. Ein möglicherweise problematischer Kandidat ist hierbei das Open-Source-Persistenz- und ORM-Framework Hibernate. Spring 6 wird Hibernate 6 und 6.1 nutzen. Alleine dafür gibt es einen umfassenden Migrationsguide.

Das Spring-Team hat alle nicht auf Jakarta EE migrierten Abhängigkeiten aus dem Dependency-Management entfernt. Anwendungen werden daher nicht mehr kompilieren und selbst nach dem manuellen Hinzufügen der jeweiligen Libraries, beispielsweise Apache ActiveMQ, sich nicht mehr automatisch konfigurieren. Sie manuell zu konfigurieren ist ebenfalls keine Lösung, da Java EE und Jakarta EE Libraries nicht gemeinsam verwendbar sind.

Die relevanten Abhängigkeiten liegen zwangsläufig in neuen Major-Versionen vor. Hersteller respektive Working Groups haben das in Teilen genutzt, um weitere (Breaking) Changes zu realisieren. Es bleibt also nicht beim simplen Suchen und Ersetzen von Package-Namen, sondern häufig sind Anpassungen der API oder Austausch und Aktualisierung weiterer Libraries notwendig: So wird aus einem Upgrade (Spring Boot 2.7 nach 3) schnell eine Kette von Upgrades.

GraalVM hat die Java-Welt in den letzten drei Jahren sehr beschäftigt. Mit dem Erscheinen von Quarkus im Frühjahr 2019 ging ein Ruck durch das Ökosystem: kürzere Start-up-Zeiten, geringerer Speicherverbrauch, reduzierte Angriffsvektoren. Native Image als Teil der GraalVM erlaubt es, JVM-basierte Programme Ahead of Time (AOT) zu kompilieren und damit ein Binary zu erzeugen, das nativ auf dem jeweiligen Betriebssystem startet. Dazu gehört nicht nur eine Teilmenge der jeweiligen JVM für das Betriebssystem, sondern auch das jeweilige Programm.

GraalVM Native Image bringt einige Restriktionen: Bei Reflection sind die zu verwendenden Klassen und Methoden vorher aufzuzählen, da die Images optimiert sind. Was nicht über eine Callstack-Analyse zur Build-Zeit erreichbar ist, befindet sich dann nicht im Image.

Während andere Frameworks darauf setzen, ihren Kontext zur Kompilierzeit hochzufahren, teilweise oder ganz als Bytecode zu serialisieren und damit möglichst viel zur Build-Zeit zu optimieren, hat das Spring-Team nach mehreren Iterationen über das experimentelle Spring-Native-Projekt einen anderen Ansatz gewählt: Die Annotationen und damit Beans, Bean-Prozessoren, Hooks und Bedingungen eines Spring-(Boot-)Kontextes werden von neuen Annotation-Prozessoren zur Kompilierzeit ausgewertet und nicht mehr über eine Reflection zur Laufzeit. Das sich daraus ergebende Bild des Spring-Kontextes wird als Sourcecode serialisiert und ist Teil des zu kompilierenden Programms. Hinzu kommt die öffentliche API, die weitere Hints an GraalVM weitergibt.

Seit jeher legt das Spring-Team viel Wert auf Testbarkeit, und das ist auch im Rahmen von Native Image so: Nicht nur hat es mit den JUnit- und GraalVM-Teams gemeinsam an der Testbarkeit und Debugbarkeit nativer Programme gearbeitet, auch der generierte Sourcecode zur statischen Definition eines Application Context ist durch Tests abgedeckt. Ferner wird er sogar mit Kommentaren generiert, sodass er lesbar, aber auch debugbar ist. Spring Boot 3 bringt all das zusammen: neue Profile und Targets für Maven- und Gradle-Plug-ins, die die native Kompilierung anstoßen, Profile für native Tests und die Integration des GraalVM-Metadata-Repository (Sammlung von Hints, die für unterschiedliche Libraries notwendig sind).

Meinung

Mit dem Umbau des Frameworks ist dem Spring-Team eine herausragende Engineeringleistung gelungen: Das zwanzig Jahre alte Framework ist seit jeher konsequent auf Reflection und die damit verbundenen Möglichkeiten der JVM ausgerichtet. Innerhalb weniger Jahre hat das Spring-Team es so umgebaut, dass es in der neuen geschlossenen Welt nicht nur voll funktionsfähig, sondern auch testbar ist. Bemerkenswert ist zudem, dass es dabei sogar noch transparenter wird (Sourcecode-Generierung, um den Kontext zu beschreiben). Für das erste Release wurde das Augenmerk nur auf Native Image gelegt, langfristig wird der statische Kontext auch im JVM-Modus Vorteile bringen. Darüber hinaus sollte die Vorgehensweise des Spring-Teams vielen anderen Teams Vorbild sein: Nicht immer ist der erste Lösungsansatz der beste, sondern nur Teil des Weges.

Observability und Declarative Web Clients stehen als große neue Features in den Release Notes. Sie sind funktional in Spring Framework 6 angesiedelt und Spring Boot 3 orchestriert sie. Observability ist die Beobachtbarkeit und Nachvollziehbarkeit von (verteilten) Ereignissen und Interaktionen infolge von Requests. Auf Basis einer neuen Hauptversion des Application-Monitoring-Tools Micrometer und neuer Aspekte (Observed) erlaubt das Framework nun Metriken, Tracing und Logging über Spans (Zeitspannen) einheitlich zu gestalten. Jede Observation findet in einem Kontext und Scope mit korrelierten Logs und Metriken statt. Die Konfiguration ist sowohl deklarativ über Annotationen möglich als auch vollständig programmatisch über eine API.

Das Besondere ist die Art und Weise der Instrumentierung. Üblicherweise geschieht das entweder über Java Agents auf Ebene des Bytecodes oder über Schnittstellen in den Libraries. Beides ist heikel: Agents müssen konfiguriert und Schnittstellen korrekt benutzt werden. Spring Framework 6 und die Portfolioprojekte instrumentieren daher direkt die relevanten Methoden in den Modulen.

Ein weiteres Feature sind Declarative Web Clients: Spring Framework erlaubt nun die interfacebasierte Deklaration von Services, die beliebige REST-Endpunkte aufrufen können, analog zu Spring Data Repository Über eine Proxy Factory sind Instanzen des Interface generier- und benutzbar. Spring Boot 3 bietet dafür Autokonfiguration:

interface RepositoryService {
    @GetExchange("/repos/{owner}/{repo}")
    Repository getRepository(@PathVariable String owner,
                             @PathVariable String repo);
}

Ein Blick auf die Release Notes von Spring Boot 3 zeigt weitere Punkte: Das empfohlene Build-Tool ist nun Gradle und es gibt keinen Support mehr für Apache ActiveMQ, Atomikos, Ehcache 2, Hazelcast 3, Apache CommonsMultipartResolver, embedded MongoDB und spring.factories (Hersteller von Third-Party-Erweiterungen müssen nun META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports nutzen). Zudem gibt es RxJava-Support nur noch ab RxJava 3. Das Spring-Team hat einige Properties umbenannt. Es gibt konsistente HTTP-Mappings für Web MVC und WebFlux, darüber hinaus sind Upgrades auf Hibernate 6.1, Flyway 9 und R2DBC 1.0 möglich.

Mit Spring Boot Migrator steht ein OpenRewrite-basiertes Werkzeug zur Verfügung, das die Migration von Properties und Abhängigkeiten vereinfacht. Das Spring-Team empfiehlt eine mehrstufige Vorgehensweise: Am Anfang steht das notwendige Upgrade auf Java 17 und anschließend können alle Aktualisierungen auf das jeweils neueste Release von Spring Boot 2.7.x inklusive der Abhängigkeiten erfolgen (falls von älteren Spring-Boot-Versionen aktualisiert werden muss: Zwischenschritte nicht überspringen). Im nächsten Schritt sind alle Stellen zu korrigieren, die in 2.7 "nur" Warnungen durch das Nutzen als veraltet (deprecated) markierter APIs verursachen und in 3.0 zu Fehlern werden würden. In Spring Web MV darf nur noch PathPatternParser anstelle von AntPathMatcher zum Einsatz kommen. Im Vorfeld sollten Entwickler noch testen, ob alle verwendeten Libraries von Dritten Jakarta-EE-9-kompatibel sind und ob aktualisierte Spring-Integrationen der externen Libraries vorliegen.

Weitere Details zu Spring Framework 6.0 lassen sich der Ankündigung im Spring-Blog entnehmen.

Michael Simons
arbeitet als Software Engineer bei Neo4j und beschäftigt sich dort mit dem Spring-Data-Modul für die Graphdatenbank Neo4j. Er ist aktueller Maintainer der Cypher-DSL und des Datenbank-Refactoring-Tools Neo4j-Migrations.
(nb)