© Enkel/Shutterstock.com
GraalVM Native Image macht Spring-Boot-Anwendungen Beine

Langsam war gestern!


Schon seit einiger Zeit macht GraalVM von sich reden. Frameworks wie Quarkus oder Micronaut stellen Rekorde auf, was die Start-up-Zeit oder den Arbeitsspeicherverbrauch von Enterprise-Java-Anwendungen angeht. Doch was wäre, wenn man den Platzhirsch Spring Boot ebenfalls als native GraalVM-App laufen lassen könnte?

Erinnern Sie sich noch an die Umstellung von den guten alten Festplatten auf SSDs? Dieser „Aha-Moment“, wenn auf einmal alle Programme unglaublich viel schneller starten als vorher? Im Grunde passiert gerade etwas Ähnliches innerhalb des Java-Ökosystems. Vor allem das GraalVM-Subprojekt namens Native Image [1] sorgt für ordentlich Schlagzeilen – inkl. aller Frameworks, die die Arbeit damit vereinfachen. Denn was da am Ende beim Kompilieren herauskommt, ist so gar nicht Java-like: Es sind kleine und extrem schnell startende ausführbare Dateien, die dem Kubernetes-Administrator nicht länger Schweißperlen wegen langer Start-up-Zeiten auf die Stirn treiben.

Doch oft fehlt es neuen Frameworks noch an Reife. Und nur weil sie ein „bisschen“ schneller starten, hat man noch nicht genug Gründe, sie auch im Projektalltag sofort einzusetzen. Doch sobald ein Schwergewicht wie Spring Boot die GraalVM-Native-Image-Unterstützung ankündigt, ändert sich das Bild. Aus vielen Projekten ist das Spring Framework sowieso nicht wegzudenken. Würde man die Vorteile der neuen JVM einfach hier einsetzen können, dann wäre mit einem Schlag eine potenziell riesige Nutzergemeinschaft da. Und genau das wurde Ende letzten Jahres auf der SpringOne 2019 angekündigt [2]. Das Spring-Entwicklungsteam ließ erste Einblicke in die Umsetzung der GraalVM-Native-Image-Unterstützung zu und nannte auch gleich noch eine Roadmap für deren produktive Verfügbarkeit: „Stable GraalVM Native Image support for Spring Boot can be expected with the Spring Framework’s 5.3 release planned in autumn 2020.“ [3]

Man darf also schon sehr bald mit einem stabilen Release rechnen. Und obwohl sich die GraalVM-Native-Image-Unterstützung noch mitten in der Entwicklung befindet, lohnt es sich bereits heute, einen Blick darauf zu werfen.

Zwei Gegensätze ziehen sich an

Der Fokus des GraalVM-Native-Image-Projekts liegt auf deutlich schnelleren Start-up-Zeiten und stark reduziertem Hauptspeicherverbrauch von Java-Programmen. Dazu wird mit Hilfe des SubstrateVM [4] genannten Frameworks eine Ahead-of-Time-Kompilierung eingeführt, die plattformspezifische native Dateien ausspuckt. Spätestens hier wird man als langjähriger Java-Nutzer aufmerken. Denn im Umkehrschluss fallen mit der Nutzung von GraalVM Native Image auch einige gewohnte Features der JVM weg. Speziell dynamische Zugriffe zur Laufzeit und Reflection müssen der SubstrateVM schonend – und vor allem bereits während des Kompilierens – beigebracht werden. Darunter fällt besonders auch das dynamische Component Scanning und die Auto Configuration, wovon Spring ja bekanntermaßen massiv Gebrauch macht.

Und genau hierauf liegt natürlich das Augenmerk der Spring-Entwickler. Es muss ein Weg gefunden werden, die vielen Automatismen aus der Laufzeit zu verbannen und in die Compile-Zeit zu verlegen. Denn damit kann GraalVM Native Image gut leben: Über statisch konfigurierte JSON-Dateien oder sogar ein dynamisches Feature API [5] können dem Kompilierungsprozess alle dynamischen Zugriffe sozusagen vorab bekannt gemacht werden. Und falls diese Maßnahmen nicht ausreichen, steht sogar noch ein Graal Native Image Agent [6] zur Verfügung, der sich eine vorher „normal“ kompilierte Anwendung zur Laufzeit genau ansehen kann, um die entsprechend notwendige Konfiguration für die Native-Image-Kompilierung zu erzeugen.

Die aktuelle Umsetzung der GraalVM-Native-Image-Unterstützung für Spring fokussiert sich auf die Implementierung eines dynamischen Features, das alle Informationen für die native GraalVM-Kompilierung sammelt. Das Projekt spring-graalvm-native [7] steht auf GitHub als experimentelles Spring-Projekt bereit und enthält den aktuellen Stand der Features. Speziell an den Beispielen des Projekts [8] kann für viele Arten von Spring-Anwendungen gut nachvollzogen werden, was für die Nutzung von GraalVM Native Image mit Spring zu tun ist. Doch natürlich wollen wir uns in diesem Artikel auch eine exemplarische Spring-Boot-Anwendung im Detail anschauen.

Ein schönes Beispiel

Das Ziel der Spring-Entwickler ist es, nicht nur neuen, sondern auch bereits existierenden Spring-Boot-Anwendungen eine native Kompilierung zu ermöglichen [9]. Für diesen Artikel soll ein eigenes Beispiel auf Basis von Spring Boot WebFlux zusammengebaut werden (eine vollständige Beispielanwendung steht auch auf GitHub bereit [10]). Und dazu starten wir natürlich mit dem Spring Initializr auf start.spring.io (Abb. 1).

hecht_graal_1.tif_fmt1.jpgAbb. 1: Der natürliche Startpunkt einer Spring-Boot-Anwendung: start.spring.io

Hier sollte ein möglichst aktuelles Spring-Boot-Release gewählt werden. Auf keinen Fall sollte die Version kleiner als 2.3.0.RELEASE sein, da der GraalVM-Support mit jeder Version stark verbessert wird und in niedrigeren Versionen teilweise nur eingeschränkt oder gar nicht funktioniert [3].

Für einen reaktiven RESTful Web Service sollte auch Spring Reactive Web als Abhängigkeit gewählt werden. Nach dem Runterladen kann in der IDE des Vertrauens auch gleich ein simpler REST Service implementiert werden. In Springs reaktiver Variante wird zuerst ein sogenannter Handler benötigt (siehe HelloHandler in Listing 1).

Listing 1

@Component public class HelloHandler { protected static String RESPONSE_TEXT= "Hello Reactive People!"; public Mono<ServerResponse> hello(ServerRequest serverRequest) { return ServerResponse .ok().contentType(MediaType.TEXT_PLAIN) .body(BodyInserters.fromValue(RESPONSE_TEXT)); }}

Als Nächstes benötigen wir auch noch einen Router, der die Weiterleitung unseres HTTP Requests an den Handler übernimmt (siehe HelloRouter in Listing 2).

Listing 2

@Component public class HelloRouter { @Bean public RouterFunction<ServerResponse> route(HelloHandler helloHandler) { return RouterFunctions.route( RequestPredicates.GET("/hello") .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), serverRequest -> helloHandler.hello(serverRequest) ); }}

Natürlich darf auch ein Testfall nicht fehlen. Hierfür kann speziell für eine vollständig reaktive Anwendung die Klasse org.springframework.test.web.reactive.server.WebTestClient sehr hilfreich sein, die die Erstellung der Tests stark vereinfacht (danke nochmal an Michael Simons für den Tipp). Eine Beispielimplementierung findet sich in der Klasse HelloRouterTest in Listing 3.

Listing 3

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class HelloRouterTest { @Test void should_call_reactive_rest_resource(@Autowired WebTestClient webTestClient) { webTestClient.get().uri("/hello") .accept(MediaType.TEXT_P...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang