Suche
Diese Features hat das Java-Application-Framework im Gepäck

Spring 5 ist da! Frühling für Enterprise Java

Oliver Gierke, Jürgen Höller, Mark Paluch

©Shutterstock/Depiano

Knapp dreieinhalb Jahre nach dem letzten Major-Release ist die fünfte Version des Java-Application-Frameworks Spring veröffentlicht worden. Im Gepäck von Spring 5: ein Baseline-Upgrade auf Java 8+, Java EE 7+ sowie Unterstützung für reaktive Programmierung und ein funktionales Webframework. Grund genug, einen genaueren Blick auf die enthaltenen Neuerungen zu werfen.

Fast fünfzehn Jahre ist es her, dass Spring das Licht der Welt erblickte. Was als Beispielcode eines Buchs und Alternative zum damaligen J2EE-Standard begann, ist nun seit gut einer Dekade das am weitesten verbreitete Framework für Java-Applikationen. In dieser Zeit hat sich die Welt der Anwendungsentwicklung in Java ebenso fundamental geändert wie die Anforderungen, denen Java-Entwickler im Arbeitsalltag ausgesetzt sind. Die bisherigen vier Spring-Generationen bieten mitsamt des gesamten Ökosystems Konsistenz in Programmierkonzepten und widmen sich gleichzeitig aktuellen Trends. Es begann mit XML und entwickelte sich über Annotationen hin zu Java-basierter Konfiguration, setzte anfangs auf sehr explizite Konfiguration und reicht nun mit Spring Boot bis zu Convention over Configuration. Schauen wir also im Detail an, was uns in Version 5 erwarten wird: Java 8, Java EE 7, reaktive und funktionale Programmierung sowie frühe Unterstützung für Java 9, Java EE 8 und HTTP/2.

Hauptversionssprünge werden von Spring üblicherweise dazu genutzt, die Anforderung an die Ablaufumgebung für die mit ihm geschriebenen Programme neu zu definieren. Spring 4 setzte bislang sowohl Java SE als auch Java EE in Version 6 voraus. Neuere Laufzeitumgebungen – insbesondere JDK 8 – wurden automatisch erkannt und entsprechend unterstützt. Die kommende Generation setzt nun auf Java SE 8 und Java EE 7 als Baseline für die Laufzeitumgebung. Das bringt weitreichende Vorteile für das Framework.

Nicht nur alle Features des JDK 8, auch Servlet-API 3.1, JMS 2.0, Bean Validation 1.1 und JPA 2.1 lassen sich endlich voraussetzen und in der gesamten Codebasis verwenden. Jedoch entfällt dadurch auch die Unterstützung für OpenJPA, da zum jetzigen Zeitpunkt keine JPA-2.1-kompatible Implementierung dazu vorliegt, sowie für ältere Servergenerationen wie Tomcat <8.0 und WebSphere <8.5. Für Drittbibliotheken werden viele Mindestversionen angehoben, z. B. Jackson ab Version 2.6 und Hibernate ab 5.0. Komplett eingestellt wird die Unterstützung für das Portlet-API, Velocity, JasperReports, XMLBeans, JDO und auch für Guava als Cacheimplementierung. Letztere wird durch die Unterstützung dessen Nachfolgers Caffeine ersetzt, die schon in 4.3 eingeführt wurde. Nutzer betroffener Bibliotheken müssen also bei Bedarf auf Spring Framework 4.3.x bleiben, das mindestens bis 2019 weitergepflegt wird.

Ähnlich wie frühere Versionen, beschränkt sich auch Spring 5 nicht auf die Unterstützung von aktuell bereits veröffentlichten Versionen von Java SE und EE. Viele Teilspezifikationen des voraussichtlich im Juli finalisierten Java EE 8 sind bereits weit fortgeschritten. Da sich die Neuerungen in vielen Fällen eher auf Anwender-APIs beziehen und sich nur sehr wenige Änderungen in den Integrations-APIs finden, kann Spring 5 diese Teilspezifikationen bereits jetzt optional unterstützen. Hier sind z. B. Bean Validation 2.0 hauptsächlich via Hibernate Validator 6.0 und das JSON-Binding-API – analog zur Unterstützung von Jackson – zu nennen.

Der interessanteste Aspekt von Java EE 8 ist aus Spring-Sicht jedoch Servlet 4 mit seiner Unterstützung für HTTP/2. Für die API-Seite bietet Servlet 4.0 den HTTP/2 PushBuilder, der jetzt auch über Spring MVC bezogen werden kann. Aber darüber hinaus kommt wenig Neues. In Bezug auf Java 9 setzt Spring primär auf Laufzeitkompatibilität, sowohl für Spring 5 als auch für 4.3.x. Auf dem neu in Java 9 eingeführten Modulpfad können die klassischen Spring-JARs als Automatic Modules betrieben werden. Damit wird Anwendungen ermöglicht, auf Jigsaw zu setzen und die dabei benötigten Frameworkmodule in ihren Moduldeskriptoren zu referenzieren.

Ebenfalls nicht unerwähnt soll die Unterstützung von JUnit 5 bleiben. Spring hatte schon immer einen großen Fokus auf Testbarkeit von Applikationscode, sowohl für Unit- als auch für containergetriebene Integrationstests. Die bisher gewohnte Unterstützung zum Starten eines ApplicationContext, das automatische Zurückrollen von Transaktionen über die Integration in JUnit 4, setzt sich in der für JUnit 5 fort. Dadurch, dass ein Mitglied des Spring-Teams signifikant in die Entwicklung von JUnit 5 eingebunden ist, finden sich in dieser neuen Generation der Testbibliothek einige Features, die stark von Spring inspiriert sind. Die Injektion von Parametern in Testmethoden erinnert nicht nur von der Art und Weise der Nutzung her an Handler-Methoden in Spring MVC. Auch das Erweiterungs-API für diesen Mechanismus ist an das entsprechende Arrangement in Spring MVC und Spring Messaging angelehnt.

Funktionale Bean-Registrierung

Die aktuelle Generation 4.x des Spring Frameworks bietet bereits weitgehende Unterstützung für Java-8-APIs und -Sprachkonstrukte im Benutzercode. Allerdings ist diese Unterstützung, z. B. für das neue Date/Time-API oder Optional, durch entsprechende Aktivierung und Überprüfungen zur Laufzeit implementiert. Das Upgrade auf Java 8 als Basislaufzeitumgebung für Spring 5 bringt neue Möglichkeiten mit sich, da nun Typen, die erst in Java 8 zur Verfügung stehen, Teil des öffentlichen Container-APIs sein können. Ein Beispiel hierfür ist eine interessante Neuerung im Funktionsumfang des ApplicationContext: funktionale Bean-Registrierung.

Es gab bisher in Spring verschiedene Möglichkeiten, dem Container Applikationskomponenten bekannt zu machen. Traditionell per XML-Konfigurationsdatei, durch Auszeichnung der Klassen mit Annotationen und ein so genanntes Component-Scanning des Containers, das bestimmte Pakete des Klassenpfads nach annotierten Klassen durchsucht. Auch @Bean-Methoden und expliziter Java-Code, in dem die Komponenten manuell instanziiert werden, sind möglich. Allen diesen Wegen ist gemein, dass sie einen indirekten Schritt darstellen, die der Container erst in eine gemeinsame Abstraktion überführt und danach als äquivalent behandelt.

Spring 5 führt nun auf GenericApplicationContext Methoden ein, die es erlauben, Bean-Definitionen direkt programmatisch zu registrieren und dabei in verschiedenen Stufen die Kontrolle über die Instanziierung der Komponente zu übernehmen (Listing 1).

GenericApplicationContext ctx = new GenericApplicationContext();

ctx.registerBean(First.class);

ctx.registerBean(Second.class,
  () -> new Second(ctx.getBean(First.class)));

ctx.registerBean(Third.class,
  () -> new Third(ctx.getBean(Second.class),
  bd -> bd.setLazyInit(true)));

Der erste Aufruf von registerBean(…) macht dem Container die Klasse als Komponente bekannt und sorgt dafür, dass sie nach den gängigen Regeln instanziiert wird. Dabei lässt sich auch reibungslos mit dem annotationsbasierten Modell interagieren. Die zweite Variante sollte genutzt werden, wenn mehr Kontrolle über die Instanziierung erwünscht ist. Ein Anwendungsfall kann hier die Nutzung spezifischer Konstruktoren sein oder auch generell der Verzicht auf Annotationen bzw. auf Reflection zur Erzeugung von Bean-Instanzen. Sollte man weitere Metadaten für die erzeugte Bean-Definition hinterlegen wollen, ist dies über weitere Lambda-Ausdrücke für BeanDefinitionCustomizer möglich.

Reaktive Programmierung mit Spring 5

Eine klassische Java-basierte Webanwendung wird üblicherweise auf dem Servlet-Stack ausgeführt: Tomcat, Jetty, WildFly, WebSphere oder WebLogic. Der Applikationscode folgt dem Programmiermodell von Spring MVC, JAX-RS oder einem anderen Webframework, typischerweise eng an das Servlet-Modell angelehnt. Bei all diesen Ansätzen ist die grundsätzliche Ablaufsemantik die gleiche. Ein Request wird an einen Java-Thread gebunden, der erst den Container durchläuft, optional ein Framework, dann Benutzercode mit Geschäftslogik und Datenzugriff. Am Ende der Verarbeitung wird ein Response erzeugt, den der Container an den Client ausliefert.

Ein Großteil der Verarbeitung besteht aus I/O-Operationen. Der Request muss vom Client auf den Server übertragen werden, Geschäftslogik interagiert mit Datenbanken, der Response wird an den Client übertragen. In diesem Modell belegt der Anwendungscode exklusiv einen Thread, während der überwiegende Teil der Verarbeitung darauf wartet, dass die I/O-Operationen abgeschlossen werden. Erst dann kann der Thread den nächsten Request abarbeiten. Die Schönheit dieses Modells liegt in seiner Einfachheit: Code wird Schritt für Schritt abgearbeitet, jede Zeile Code erzeugt ein Ergebnis, das in der nächsten verwendet werden kann. Nachteilig ist jedoch, dass die Serverressourcen, die für den Thread zur Verfügung gestellt werden müssen, während der Wartezeiten ungenutzt bleiben.

Reaktive Programmierung dreht dieses Prinzip um. Anwendungscode belegt nicht länger exklusiv einen Thread und gibt ihn nach getaner Arbeit frei, sondern es ist die Laufzeitumgebung, die dem Code Threadressourcen zuweist, sobald Ressourcen bereitstehen und Code ausführen können. Um dies zu realisieren, erfordert reaktive Programmierung ein Programmiermodell, das eine funktionale Deklaration von Anweisungen unterstützt. Dieser Code nimmt nicht länger an, selbst über die Threadverwendung zu bestimmen, sondern lässt die Laufzeit die deklarierten Funktionen als Callbacks aufrufen. Der Code reagiert auf die Verfügbarkeit von Ressourcen.

Dieser Fakt zieht wiederum nach sich, dass reaktiver Code eigentlich in zwei Phasen abgearbeitet wird: der reaktiven Ablaufsequenz und die schlussendliche Ausführung. Gerade erstere verlangt, dass Code in einer Art und Weise geschrieben ist, dass er die Möglichkeit bietet, Umformungsschritte zu definieren, sie aber noch nicht auszuführen. In gewisser Weise kann man hier Parallelen zum in Java 8 eingeführten Stream-API finden, in dem Schritte wie Filter, Transformationen mittels map(…) und flatMap(…) beschrieben werden und erst zur Ausführung gelangen, sobald eine terminierende Operation wie collect(…) aufgerufen wird. Was dem Stream-API jedoch fehlt, ist das Konzept der Back Pressure, das erheblich an Bedeutung gewinnt, wenn die zu verarbeitenden Datenströme direkt aus Ressourcen bezogen werden.

Wenn also ein Stream nicht ausreicht, was bietet sich als Alternative? Dezidierte reaktive Bibliotheken für Java gibt es bereits seit einiger Zeit. Die wohl bekannteste ist RxJava. Spring 5 nutzt allerdings eine neuere reaktive Bibliothek namens Project Reactor, ebenfalls ein von Pivotal gesponsertes Open-Source-Projekt. Die Entscheidung für Reactor fiel vor allem aus einem wesentlichen Grund: Reactor 3 wurde bewusst für die serverseitige Verwendung mit Reactive Streams auf Java 8 gebaut und ist dadurch wesentlich kleiner und fokussierter, nicht zuletzt auch in seiner API-Oberfläche. Die Reactor-Typen Flux und Mono bieten sich deshalb auch für Applikationscode an. Alternativ dazu können entsprechende Handler-Methoden auch mit RxJava-Typen wie Observable oder Flowable deklariert werden, die von Spring zur Laufzeit automatisch adaptiert werden.

Werfen wir doch einmal einen Blick auf ein kleines Beispiel, um die grundsätzlichen Konzepte und Typen zu beleuchten, die einem in der reaktiven Welt begegnen:

Mono.just("Hello")
  .map(word -> word.concat(", World!"))
  .subscribe(System.out::println);

Flux.just("Hello", "World")
  .flatMap(word -> Flux.fromArray(word.split("")))
  .subscribe(System.out::println);

Während man in der imperativen Welt von einem einzelnen Objekt oder einer Collection von Objekten spricht, kreist die reaktive um Events. Reactor stellt als Basisabstraktionen die Typen Mono, ein Stream, der entweder keinen oder exakt einen Event produziert,  und Flux, ein Stream mit bis zu unendlich vielen Events, bereit. Im ersten Fall starten wir also mit einem einfachen Hello und definieren danach einen Transformationsschritt, der dem String ein Suffix verpasst. Wichtig ist, dass das Event an dieser Stelle noch nicht erzeugt wurde und somit auch noch keine Transformation stattfand. Die ursprünglichen Daten werden zudem nicht verändert, sondern die Funktion erzeugt ein neues Objekt. Erst der finale Aufruf von subscribe(…) sorgt dafür, dass Hello publiziert, transformiert und schlussendlich als Hello, World! konsumiert wird. Im zweiten Fall erzeugt der Transformationsschritt über flatMap(…) einen Stream, der jeden Eingangsstring in seine einzelnen Buchstaben zerlegt und ab diesem Operator einen neuen Eventstream daraus macht. Auch hier wird der Stream erst ausgeführt, wenn subscribe(…) aufgerufen wird. Für einen tieferen Einblick in reaktive Programmierung sei die Blogserie von Dave Syer im Spring-Blog empfohlen.

Reaktive Programmierung in Enterprise-Unternehmen

Das Spring-Team stellte sich vor circa anderthalb Jahren die Frage, ob eine Integration dieses Paradigmas in Spring als Applikationsframework Sinn ergibt und wie diese Unterstützung genau aussehen könnte. Dabei spielte eine Reihe von Faktoren eine Rolle, die speziellen Eigenheiten des Frameworks unterliegen und sowohl die Umsetzung als auch das Timing entscheidend beeinflusst haben. Spring kommt häufig in eher konservativen Unternehmenskontexten zum Einsatz, also in Unternehmen, die neue technologische Trends eher später aufnehmen. Dieser Effekt verstärkt sich weiter, wenn diese Trends mit starken konzeptionellen Änderungen einhergehen, die den Applikationscode betreffen. Der Trend zu Microservices als Architekturparadigma hält auch in diesem Unternehmenskontext Einzug und sorgt dafür, dass es Komponenten des Gesamtsystems gibt, die eher infrastruktureller Natur sind und Teil einer Kaskade an Systemaufrufen. Diese Systeme stehen üblicherweise unter erhöhten Lastanforderungen, und es ist für sie wichtig, wenig Latenz zu verursachen. Ein weiterer Effekt des Trends hin zu kleineren Systemen ist, dass die Anzahl der zu betreibenden Systeme umgekehrt proportional größer wird. Das heißt, man möchte unter Umständen auf dem gleichen Satz Hardwareressourcen eine größere Anzahl Systeme betreiben. Das verstärkt den Wunsch nach effizienter Ressourcennutzung.

Spring WebFlux: ein reaktives Webframework

Diese Aspekte sorgten dafür, dass sich das Spring-Team die Frage stellte, wie eine reaktive Version von Spring MVC aussehen könnte, die es den Nutzern erlaubt, bereits bestehendes Wissen in diese neue Welt zu transferieren. Jedoch dabei immer beachtend, dass der Wechsel zu diesem neuen Programmierparadigma auch signifikante Änderung im Umgang nicht nur mit Ressourcen und Transaktionen benötigt. Entstanden ist Spring WebFlux, eine Alternative zum klassischen Spring Web MVC Framework. Das Wort Alternative ist hier bewusst gewählt, da Spring WebFlux kein einfacher Ersatz für Spring MVC ist. Vielmehr muss eine sehr bewusste Entscheidung getroffen werden, auf welchem Stack eine Applikation aufgesetzt werden soll, da diese Entscheidung sehr weitreichende Konsequenzen hat. Denn das veränderte Programmiermodell verlangt von Entwicklern, sich auf dieses einzulassen. Die konzeptionelle Umkehr in der Verarbeitung setzt voraus, dass man in dieser Art denkt. Tests und das Debuggen von Code werden komplexer. Viele bestehende APIs und Konzepte passen nicht zum reaktiven Modell. Datenzugriffs-APIs für relationale Datenbanken wie JDBC und JPA sind zurzeit blockend. Das Konzept von Transaktionen – das zeitweise Blockieren von Ressourcen – steht dem reaktiven Paradigma fundamental entgegen. Das heißt, eine reaktive Applikation setzt üblicherweise eher auf nicht relationale Datenbanken mit entsprechend reaktiven Treibern. Das wiederum heißt, dass ein Wechsel hin zu diesem Programmierparadigma nicht nur Änderungen in der Applikation, sondern auch bei der Datenhaltung, der Infrastruktur und dem Betrieb bedeutet. Es stellt sich die grundsätzliche Frage, ob die Vorteile, eine Applikation reaktiv zu entwickeln, diese zusätzlichen Aufwände zurzeit rechtfertigen. Die Entscheidung zwischen Spring MVC und WebFlux sollte also keine willkürliche sein. Spring-Framework-Projektleiter Jürgen Höller beleuchtet das Thema der architektonischen Entscheidungsfindung detaillierter in einem Interview von Software Engineering Radio. Wie sieht denn nun ein Controller in Spring WebFlux aus? Schauen wir uns doch einfach mal einen an (Listing 2).

@Controller
class ReactiveUserController {

  private final UserRepository repository;

  ReactiveUserController(UserRepository repository) {
    this.repository = repository;
  }

  @GetMapping("/users")
  Flux<User> getUsers() {
    return this.repository.findAll();
  }

  @PostMapping("/users")
  Mono<User> createUser(@RequestBody Mono<User> user) {
    return this.repository.save(user);
  }

  @GetMapping("/users/{id}")
  Mono<User> getUser(@PathVariable Long id) {
    return this.repository.findById(id);
  }
}

Überraschenderweise sieht dieser reaktive Controller auf den ersten Blick nicht viel anders aus als ein gewohnter Spring-MVC-Controller. Die Klasse wird durch eine Stereotypannotation dem Container bekannt gemacht und bezieht Abhängigkeiten wie gewohnt durch konstruktorbasierte Dependency Injection. Requests werden durch Annotationen an Methoden gebunden, beziehen Request-Artefakte (Pfad- bzw. Request-Parameter, den Request-Body, Header usw.) über Methodenparameter, nutzen in der Methode die Abhängigkeiten, um Businesslogik auszuführen, und produzieren ein Ergebnis, das wiederum vom Framework entsprechend in einen Response übersetzt wird. Lediglich die Nutzung der reaktiven Wrapper-Typen Mono und Flux scheinen ungewohnt.

Tatsächlich ist es allerdings so, dass sich hier hinter der Fassade die Art der Abarbeitung vollständig ändert. In einer reinen reaktiven Ablaufumgebung wird nun eben nicht mehr ein Request an einen Thread gebunden, dessen Ausführung dann das Framework durchläuft, irgendwann den Controller erreicht und jede Zeile Code Schritt für Schritt abarbeitet. In der reaktiven Welt wird die Controllermethode, die den Request schlussendlich abarbeiten wird, erst einmal nur Teil einer Abarbeitungskette. Der entscheidende Unterschied zu unserem Beispiel von oben ist, dass der Applikationscode selbst gar nicht subscribe(…) aufruft. Dies macht schlussendlich die Infrastruktur, die dann auch implizit die Flusskontrolle zwischen dem Client und potenziell involvierten Datenbanken übernimmt.

Dies funktioniert jedoch nur, wenn die eigentliche Kette des Applikationscodes  –  das heißt, auch der Code, der von den Controllermethoden aufgerufen wird – nicht blockiert. Er stößt nicht bereits Code an, der irgendwie mit Ressourcen interagiert, auf die gewartet werden muss. Wie im Beispiel ersichtlich, ist das API des Repository-Interface so gestaltet, dass es sowohl reaktive Typen entgegennimmt – wie in createUser(…), das ein Mono<User> in save(…) hinein reicht, – als auch erzeugt.

Gerade die Nutzung dieser Typen auf der Parameterseite erlaubt hier den Paradigmenwechsel. Das Framework muss zu diesem Zeitpunkt noch nicht den User aus dem Request materialisiert haben, sondern drückt eigentlich lediglich aus, dass dieser irgendwann zur Verfügung stehen wird. Dies sorgt dafür, dass die Implementierung des Repository-Interface die eigentliche Ausführung als Transformationsschritt der Abarbeitungskette umsetzen kann und muss – vorausgesetzt, die zugrunde liegende Persistenztechnologie unterstützt selbst dieses Programmierparadigma.

Reaktive Ablaufumgebungen

Die fundamentale Abkehr vom Servlet-API für den Wechsel hin zu reaktiver Programmierung sorgt natürlich auch dafür, dass neue Anforderungen an die Ablaufumgebung auftauchen. Idealerweise läuft eine reaktive Anwendung in einem reaktiven Applikationsframework auf einem Server, der für dieses Ausführungsmodell gebaut wurde. Hier bietet sich natürlich Netty als Referenz an. Aber auch die jüngsten Versionen von Tomcat und Jetty können mit ihrer Unterstützung für asynchrone Streams und effiziente Datenpuffer als reaktive HTTP-Server genutzt werden – ganz abseits des klassischen Servlet-Modells. Spring WebFlux bietet daher explizite Unterstützung für Tomcat und Jetty sowie für Netty und Undertow an, kann aber auch an weitere Container über Servlet 3.1 Async I/O angebunden werden.

Funktionales Webframework

Mit der Veröffentlichung von Java 8 haben erstmalig funktionale Konzepte Einzug in die Sprache gehalten, allen voran Lambdas und Streams. Dies ist ein ähnlich signifikanter Schritt wie die Einführung von Annotationen in Java 5. Solcherlei Sprachfeatures sind üblicherweise besonders für die Generation an Frameworks prägend, die in dieser Zeit entstehen. Annotationen sind aus Java-Webframeworks heutzutage nicht mehr wegzudenken. Sie bedingen jedoch die Auswertung und Ausführung von Benutzercode per Reflection.

Mit Java 8 als Voraussetzung für Spring 5 stellte sich also die Frage, inwieweit funktionale Konzepte designgebend für ein Java-basiertes Webframework sein könnten. Auch hier beginnt die Suche nach Antwort natürlich nicht bei null. Besonders Frameworks alternativer JVM-Sprachen, in denen funktionale Konzepte bereits seit viel längerer Zeit zur Verfügung stehen, konnten hier als Inspirationsquelle dienen.

Entstanden ist eine funktionale Alternative zum annotationsbasierten Controllermodell in Spring WebFlux. Das API implementiert einen durch und durch funktionalen Blick auf die Abarbeitung eines HTTP Requests. Im Grunde ist sie nichts anderes als eine Funktion von einem Request auf einen Response. Damit dreht sich in diesem Ansatz eigentlich alles um Code, der genau das implementiert, z. B. über eine Methode einer ganz gewöhnlichen Spring-Komponente (Listing 3).

@Component
class FunctionalUserController {

  private final UserRepository repository;

  FunctionalUserController(UserRepository repository) {
    this.repository = repository;
  }

  Mono<ServerResponse> getUser(ServerRequest request) {

    Mono<User> user = request.pathVariable("id")
      .then(repository::findById);

    return ServerResponse.ok().body(user, User.class);
  }

  Mono<ServerResponse> getUsers(ServerRequest request) {

    Flux<User> users = repository.findAll();
    return ServerResponse.ok().body(users, User.class);
  }
}

Mit @Component sorgen wir dafür, dass eine Instanz dieser Klasse Spring Bean wird. Hier könnte bereits auch alternativ die funktionale Bean-Registrierung zum Einsatz kommen. Die Methoden selbst sehen normalen Spring-MVC-Controller-Methoden gar nicht so unähnlich. Es gibt jedoch ein paar auffällige Unterschiede. Die Methoden tragen keine Annotationen, die Requests an die Methodenausführung binden. Hier muss also offensichtlich eine andere Lösung im Spiel sein, doch dazu gleich mehr. Es findet ebenfalls kein annotationsbasiertes Binden von Parametern, dem Request-Body oder Ähnlichem statt. Es wird lediglich ein ServerRequest in die Methode gereicht, der dann Methoden zur Verfügung stellt, um diese Request-Artefakte zu beziehen. Die Methode erzeugt in ihren Verarbeitungsschritten wieder reaktive Objekte und final ein Mono, um die Funktion zu vollenden. Dies sorgt dafür, dass die Methoden Teil einer reaktiven Abarbeitungspipeline werden können.

Wie erfolgt nun aber das Mapping von empfangenen Requests auf diese Methoden? Spring 5 stellt dazu eine Abstraktion namens RouterFunctions zur Verfügung, die es ermöglicht, die Verknüpfungen von Request-Charakteristika, wie Pfad-Mapping oder HTTP-Methode, und auszuführender HandlerFunction herzustellen. HandlerFunction ist praktischerweise ein funktionales Interface mit einer Methode Mono handle(ServerRequest), sodass wir unsere implementierten Methoden per Java-8-Methodenreferenz übergeben können:

RouterFunction<?> router = RouterFunctions
  .route(GET("/users"), delegate::getUsers)
  .andRoute(GET("/users/{id}"), delegate::getUser);

Der einfachste Weg, diese RouterFunction dem Framework bekannt zu geben, ist, sie in einer Spring-Boot-Applikation dem Framework in gewohnter Manier per @Bean-Methode zu deklarieren:

@SpringBootApplication
class ApplicationConfiguration {

  @Bean
  RouterFunction<?> routes(FunctionalUserController controller) {

    return RouterFunctions
      .route(GET("/users"), controller::getUsers)
      .andRoute(GET("/users/{id}"), controller::getUser);
  }
}

Ein recht offensichtlicher Vorteil ist hier, dass sowohl die Auswertung des Mappings analog zu einem Spring MVC HandlerMapping als auch die schlussendliche Ausführung der HandlerFunction, die in Spring MVC ein HandlerAdapter übernommen hätte, komplett ohne Reflection auskommt.

Neu dabei: Kotlin Extensions

Während die überwiegende Mehrheit der Spring-Applikationen weltweit wohl in Java implementiert ist, gab es auch immer dezidierte Unterstützung für dynamische JVM-Sprachen. Für Groovy gibt es z. B. einen speziellen BeanDefinitionReader, der es ermöglicht, Bean-Definitionen per Groovy DSL zu deklarieren. Ebenso lassen sich dynamische Groovy- und JRuby-Skripte in Spring-Applikationen integrieren.

Kotlin aus dem Hause JetBrains ist eine vergleichbar junge kompilierte Sprache, die sich zurzeit rasant wachsender Beliebtheit erfreut. Es spricht also erst einmal nichts dagegen, einfach Applikationsklassen in Kotlin zu implementieren und sie mit Spring zu betreiben. Argumente hierfür sind üblicherweise eine etwas kürzere, prägnantere Syntax und bequemere Sprachfeatures. Kotlin selbst bietet jedoch ein spezielles Sprachfeature, so genannte Extensions, mit denen bestehende Typen mit zusätzlichen Methoden angereichert werden können. Das kann die Benutzung von APIs etwas bequemer machen. Spring 5 liefert einige solche Extensions mit aus, sodass sich hier und da noch idiomatischerer Kotlin-Code bei der Verwendung von Spring-APIs ergibt.

Ein gutes Beispiel hierfür ist das oben eingeführte API zum programmatischen Registrieren von Bean-Definitionen. GenericApplicationContext exponiert eine Methode registerBean(Class<T>, Supplier<T>, BeanDefinitionCustomizer). Signifikant ist hier, dass die Methode den Typ der erzeugten Bean-Instanz zusätzlich als ersten Parameter übergeben bekommen muss, da sich zur Laufzeit aus dem Supplier der Parametertyp nicht mehr ermitteln lässt. In Kotlin gibt es jedoch Typparameter-Reifizierung. Das bedeutet, dass in Kotlin dieser erste Parameter eigentlich nicht notwendig ist. Die von Spring 5 bereitgestellte Kotlin Extension fügt GenericApplicationContext eine weitere Kotlin-spezifische Methode hinzu, die nun ohne den zusätzlichen Typparameter auskommt, ihn aus der deklarierten Kotlin-Funktion extrahiert und dann an die bereits existierende Methode delegiert.

Funktionale Bean-Registrierung in Kotlin.
val ctx = GenericApplicationContext()
ctx.registerBean { Bar(ctx.getBean(Foo::class)) }

Als vertiefende Lektüre seien hier Klassen im Spring Framework, die auf den Namen Extensions.kt enden empfohlen, z. B. GenericApplicationContextExtensionsKt.

Fazit

Kurzum: Kaum ein anderes Stück Java-Technologie ist sich über eine so lange Zeit treu geblieben und hat sich gleichzeitig so fundamental neu erfunden. Mit Spring 5 erwartet uns eine neue Generation des Applikationsframeworks, die aktuelle Sprach- und API-Spezifikationen aufgreift und mit Spring WebFlux reaktive und funktionale Programmierkonzepte auch für Unternehmen interessant macht. Den Beispielcode zu Spring WebFlux finden Sie auf GitHub.

Geschrieben von
Oliver Gierke
Oliver Gierke
Oliver Gierke ist Teil des Spring-Data-Teams bei SpringSource, a division of VMware und leitet dort das JPA-, MongoDB- und Core-Modul. Seit über sechs Jahren widmet er sich dem Entwickeln von Java-Enterprise-Applikationen, Open-Source-Projekten und ist Mitglied der JPA 2.1 Expert Group. Seine Arbeitsschwerpunkte liegen im Bereich Softwarearchitektur, Spring und Persistenztechnologien. Er ist regelmäßiger Sprecher auf deutschen und internationalen Konferenzen sowie Autor von Fachartikeln.
Jürgen Höller
Jürgen Höller
Jürgen Höller ist Mitbegründer des Open-Source-Projekts Spring Framework und begleitet das Kernprojekt bereits seit 2003 als Projektleiter und Release-Manager. Er ist ein erfahrener Softwarearchitekt und Berater mit besonderer Expertise in Codeorganisation, Transaktionsmanagement und Messaging in Unternehmensanwendungen.
Mark Paluch
Mark Paluch
Mark Paluch ist Software Craftsman, Spring Data Engineer bei Pivotal, entwickelt den lettuce-Redis-Treiber und arbeitet als Mitglied der CDI 2.0 (JSR 365) Expert Group an der nächsten Version der Spezifikation. Er entwickelt seit 1999 Java-Server-, Frontend- und Webanwendungen. Sein Fokus liegt auf reaktiven Systemen, Spring und Redis.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.