Spring Webflux mit Kotlin

Frühlings-Webflux mit Kotlin

1. Überblick

In diesem Tutorial zeigen wir Ihnen, wie Sie das Spring WebFlux-Modul mit der Programmiersprache Kotlin verwenden.

Wir veranschaulichen, wie die Annotation-basierten und Lambda-basierten Stilansätze zum Definieren der Endpunkte verwendet werden.

2. Frühling WebFlux und Kotlin

Mit der Veröffentlichung von Spring 5 wurden zwei neue Funktionen eingeführt, darunter die native Unterstützung des reaktiven Programmierparadigmas und die Möglichkeit, die Programmiersprache Kotlin zu verwenden.

In diesem Tutorial wird davon ausgegangen, dass wir die Umgebung bereits konfiguriert haben (konsultieren Sieone of our tutorials zu diesem Thema) und die Kotlin-Sprachsyntax verstehen (another tutorial zum Thema).

3. Anmerkungsbasierter Ansatz

Mit WebFlux können wir die Endpunkte definieren, die die eingehenden Anforderungen auf bekannte Weise verarbeiten sollen, indem wir die SpringMVC-Framework-Annotationen wie@RequestMapping oder@PathVariable oder Convenience-Annotationen wie@RestController oder@GetMappingverwenden. s.

Trotz der Tatsache, dass die Annotationsnamen identisch sind, machen dieWebFlux’s die Methoden zunon-blocking.

Zum Beispiel dieser Endpunkt:

@GetMapping(path = ["/numbers"],
  produces = [MediaType.APPLICATION_STREAM_JSON_VALUE])
@ResponseBody
fun getNumbers() = Flux.range(1, 100)

Erzeugt einen Stream mit ersten ganzen Zahlen. Wenn der Server auflocalhost:8080 ausgeführt wird, stellen Sie über den folgenden Befehl eine Verbindung zu ihm her:

curl localhost:8080/stream

druckt die gewünschten Nummern aus.

4. Lambda-basierter Ansatz

Ein neuerer Ansatz bei der Definition der Endpunkte ist die Verwendung von Lambda-Ausdrücken, die in Java seit Version 1.8 vorhanden sind. Mit Hilfe von Kotlin können Lambda-Ausdrücke auch mit früheren Java-Versionen verwendet werden.

In WebFlux sind die Routerfunktionen Funktionen, die durchRequestPredicate (mit anderen Worten, wer die Anforderung verwalten soll) undHandlerFunction (mit anderen Worten, wie die Anforderung bearbeitet werden soll) bestimmt werden.

Eine Handlerfunktion akzeptiert die Instanz einesServerRequestund erzeugt eineMono<ServerResponse>.

In Kotlin sind if the last function argument is a lambda expression, it can be placed outside the parentheses.

Mit einer solchen Syntax können wir die Aufteilung zwischen dem Anforderungsprädikat und der Handlerfunktion hervorheben

router {
    GET("/route") { _ -> ServerResponse.ok().body(fromObject(arrayOf(1, 2, 3))) }
}

für Routerfunktionen.

Die Funktion hat ein klar lesbares Format: Sobald eine Anforderung vom Typ GET an / route ankommt, erstellen Sie eine Antwort (ignorieren Sie den Inhalt der Anforderung - daher der Symbol-Unterstrich) mit dem HTTP-Status OK und mit einem aus dem gegebenes Objekt.

Damit es in WebFlux funktioniert, sollten wir die Router-Funktion in eine Klasse einordnen:

@Configuration
class SimpleRoute {

    @Bean
    fun route() = router {
        GET("/route") { _ -> ServerResponse.ok().body(fromObject(arrayOf(1, 2, 3))) }
    }
}

Häufig erfordert die Logik unserer Anwendungen, dass wir komplexere Routerfunktionen erstellen.

In WebFlux definiert Kotlins Routerfunktion DSL eine Vielzahl von Funktionen wieaccept,and,or,nest,invoke,GET,POST mittelsextension functions, mit dem wir zusammengesetzte Routerfunktionen konstruieren können:

router {
    accept(TEXT_HTML).nest {
        (GET("/device/") or GET("/devices/")).invoke(handler::getAllDevices)
    }
}

Die Variablehandler sollte eine Instanz einer Klasse sein, die eine MethodegetAllDevices() mit der StandardsignaturHandlerFunction implementiert:

fun getAllDevices(request: ServerRequest): Mono

wie wir oben erwähnt haben.

Um eine ordnungsgemäße Trennung der Bedenken zu gewährleisten, können wir die Definitionen von nicht verwandten Routerfunktionen in separate Klassen einteilen:

@Configuration
class HomeSensorsRouters(private val handler: HomeSensorsHandler) {
    @Bean
    fun roomsRouter() = router {
        (accept(TEXT_HTML) and "/room").nest {
            GET("/light", handler::getLightReading)
            POST("/light", handler::setLight)
        }
    }
    // eventual other router function definitions
}

Wir können auf die Pfadvariablen mit derString-bewerteten MethodepathVariable() zugreifen:

val id = request.pathVariable("id")

während der Zugang vonServerRequest zum Körper mittels der MethodenbodyToMono undbodyToFlux erreicht wird, d.h.

val device: Mono = request
  .bodyToMono(Device::class.java)

5. Testen

Um die Routerfunktionen zu testen, sollten wir eineWebTestClient-Instanz der RouterSimpleRoute().route()erstellen, die wir testen möchten:

var client = WebTestClient.bindToRouterFunction(SimpleRoute().route()).build()

Jetzt können Sie testen, ob die Handlerfunktion des Routers den Status OK zurückgibt:

client.get()
  .uri("/route")
  .exchange()
  .expectStatus()
  .isOk

Die WebTestClient-Schnittstelle definiert die Methoden, mit denen wir alle HTTP-Anforderungsmethoden wieGET,POST undPUT testen können, auch ohne den Server auszuführen.

Um den Inhalt des Antwortkörpers zu testen, möchten wir möglicherweise die Methodejson()verwenden:

client.get()
  .uri("/route")
  .exchange()
  .expectBody()
  .json("[1, 2, 3]")

6. Fazit

In diesem Artikel wurde gezeigt, wie die Grundfunktionen des WebFlux-Frameworks mit Kotlin verwendet werden.

Wir haben kurz den bekannten Annotation-basierten Ansatz bei der Definition der Endpunkte erwähnt und mehr Zeit aufgewendet, um zu veranschaulichen, wie diese mithilfe von Routerfunktionen in einem Lambda-basierten Ansatz definiert werden.

Alle Codefragmente befinden sich in unserem Repositoryover on GitHub.