Reactive programming is about non-blocking applications. And Spring Framework 5 includes a new spring-webflux module, supports Reactive Streams for communicating backpressure across async components and libraries. So in the tutorial, JavaSampleApproach will guide you through the steps for creating a Kotlin Spring WebFlux Annotation-Based RestAPIs with SpringBoot 2.
Related posts:
– Kotlin Spring MVC RequestMapping RESTful APIs with @GetMapping, @PostMapping, @PutMapping, @DeleteMapping | SpringBoot Example
Contents
I. Technologies
– Java: 1.8
– Maven: 3.5.2
– Spring Tool Suite: Version 3.9.0.RELEASE
– Spring Boot: 2.0.0.M7
– Kotlin 1.1.61
– Spring Boot Starter Webflux
– Postman client
II. Spring WebFlux
Spring Framework 5.0 supports WebFlux with fully asynchronous and non-blocking and does NOT require the Servlet API(Unlike Spring MVC).
Spring WebFlux supports 2 distinct programming models:
– Annotation-based with @RestController
– Functional with Java 8 lambda style
In the tutorial, we will introduce WebFlux with Annotation-based and Kotlin language.
For starting with WebFlux, SpringBoot supports a collection dependency: spring-boot-starter-webflux.
Kotlin sample code:
import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @RestController @RequestMapping(value="/api/customer") class RestControllerAPIs { @GetMapping fun getAll(): Flux{ return Flux.fromIterable(ArrayList(custStores.values)); } @GetMapping("/{id}") fun getCustomer(@PathVariable id: Long): Mono { return Mono.justOrEmpty(custStores.get(id)) } ... }
– reactor.core.publisher.Flux
: is a standard Publisher representing a reactive sequence of 0..N items, optionally terminated by either a success signal or an error.
– reactor.core.publisher.Mono
: Mono is a specialized Publisher that emits at most single-valued-or-empty result.
III. Practice
In the tutorial, We create a Kotlin SpringBoot 2 project as below:
Step to do:
– Create Kotlin SpringBoot project
– Create data models
– Implement Spring WebFlux APIs
– Run and check results
1. Create Kotlin SpringBoot project
Using SpringToolSuite, create a Kotlin SpringBoot 2 project with Reactive Web dependency spring-boot-starter-webflux
:
org.springframework.boot spring-boot-starter-parent 2.0.0.M7 true UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-webflux org.jetbrains.kotlin kotlin-stdlib-jre8 org.jetbrains.kotlin kotlin-reflect org.springframework.boot spring-boot-starter-test test io.projectreactor reactor-test test ${project.basedir}/src/main/kotlin ${project.basedir}/src/test/kotlin org.springframework.boot spring-boot-maven-plugin kotlin-maven-plugin org.jetbrains.kotlin spring org.jetbrains.kotlin kotlin-maven-allopen ${kotlin.version} spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false
2. Create data models
– Create Address data model:
package com.javasampleapproach.webflux.model data class Address( var street : String? = null, var postcode : String? = null ){}
– Create Customer data model:
package com.javasampleapproach.webflux.model data class Customer( var id: Long = -1, var firstName: String? = null, var lastName: String? = null, var age: Int? = null, var address: Address = Address()) { }
3. Implement Spring WebFlux APIs
package com.javasampleapproach.webflux.controller import javax.annotation.PostConstruct import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import com.javasampleapproach.webflux.model.Customer import com.javasampleapproach.webflux.model.Address import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @RestController @RequestMapping(value="/api/customer") class RestControllerAPIs { val log = LoggerFactory.getLogger(RestControllerAPIs::class.java); // Define customer storage val custStores = mutableMapOf() @PostConstruct fun initial(){ custStores.put(1, Customer(1, "Jack", "Smith", 20, Address("NANTERRE CT", "77471"))) custStores.put(2, Customer(2, "Peter", "Johnson", 25, Address("W NORMA ST", "77009"))) } @GetMapping fun getAll(): Flux { return Flux.fromIterable(ArrayList(custStores.values)); } @GetMapping("/{id}") fun getCustomer(@PathVariable id: Long): Mono { return Mono.justOrEmpty(custStores.get(id)) } @PostMapping("/post") fun postCustomer(@RequestBody customer: Customer): Mono >{ // do post custStores.put(customer.id, customer) log.info("########### POST:" + customer) return Mono.just(ResponseEntity("Post Successfully!", HttpStatus.CREATED)) } @PutMapping("/put/{id}") fun putCustomer(@PathVariable id: Long, @RequestBody customer: Customer): Mono >{ // reset customer.Id customer.id = id; if(custStores.get(id) != null){ custStores.replace(id, customer) }else{ customer.id = id custStores.put(id, customer) } log.info("########### PUT:" + customer) return Mono.just(ResponseEntity(customer, HttpStatus.CREATED)) } @DeleteMapping("/delete/{id}") fun deleteCustomer(@PathVariable id: Long): Mono > { val cust = custStores.remove(id) if(cust != null){ log.info("########### DELETE:" + cust) }else{ log.info("########### Don't exist any customer with id = ${id}") } return Mono.just(ResponseEntity("Delete Succesfully!", HttpStatus.ACCEPTED)) } }
4. Run and check results
Run the Kotlin SpringBoot project with commandline: mvn spring-boot:run
-> See console’ logs:
s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/api/customer],methods=[GET]}" onto public reactor.core.publisher.Fluxcom.javasampleapproach.webflux.controller.RestControllerAPIs.getAll() s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/api/customer/delete/{id}],methods=[DELETE]}" onto public reactor.core.publisher.Mono > com.javasampleapproach.webflux.controller.RestControllerAPIs.deleteCustomer(long) s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/api/customer/{id}],methods=[GET]}" onto public reactor.core.publisher.Mono com.javasampleapproach.webflux.controller.RestControllerAPIs.getCustomer(long) s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/api/customer/post],methods=[POST]}" onto public reactor.core.publisher.Mono > com.javasampleapproach.webflux.controller.RestControllerAPIs.postCustomer(com.javasampleapproach.webflux.model.Customer) s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/api/customer/put/{id}],methods=[PUT]}" onto public reactor.core.publisher.Mono > com.javasampleapproach.webflux.controller.RestControllerAPIs.putCustomer(long,com.javasampleapproach.webflux.model.Customer)
– Make a GET all customer request: http://localhost:8080/api/customer
– Make a GET customer request: http://localhost:8080/api/customer/1
– Make a POST request: http://localhost:8080/api/customer/post
– Make a PUT request: http://localhost:8080/api/customer/put/3
– Make a DELETE request: http://localhost:8080/api/customer/delete/1
– Make a GET all customers request again: http://localhost:8080/api/customer
-> Logs from Kotlin SpringBoot server:
c.j.w.controller.RestControllerAPIs : ########### POST:Customer(id=3, firstName=Mary, lastName=Taylor, age=27, address=Address(street=S NUGENT AVE, postcode=77571)) c.j.w.controller.RestControllerAPIs : ########### PUT:Customer(id=3, firstName=Amy, lastName=Taylor, age=24, address=Address(street=E NAVAHO TRL, postcode=77449)) c.j.w.controller.RestControllerAPIs : ########### DELETE:Customer(id=1, firstName=Jack, lastName=Smith, age=20, address=Address(street=NANTERRE CT, postcode=77471))
IV. Sourcecode
SpringBootKotlinWebFluxAnnotationBased