Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Spring 5 and Kotlin

Spring 5 and Kotlin

With Spring 5 now having dedicated Kotlin support, let’s have a look into the features that are designed to make these technologies work seamless together.

Filip Maelbrancke

September 29, 2017
Tweet

More Decks by Filip Maelbrancke

Other Decks in Programming

Transcript

  1. SPRING 5 WHAT’S NEW? ▸ Core framework revision ▸ Core

    container updates ▸ Reactive programming model
  2. SPRING 5 REACTOR Reactive core non-blocking foundation
 interacts with Java

    8
 functional API, Completable
 Future, Streams Non-blocking IPC suited for microservices architecture
 backpressure-ready network engines
 (HTTP / Websockets / TCP / UDP)
 reactive encoding/decoding Typed [0|1|N] sequences reactive composable API
 Flux[N]
 Mono[0/1]
 implements Reactive Extensions
  3. SPRING 5 REACTIVE STREAMS PUBLISHER SUBSCRIBER Subscribe then
 request(n) data


    (Backpressure) 0..N data then
 0..1 (Error | Complete)
  4. SPRING 5 WHAT’S NEW? ▸ Core framework revision ▸ Core

    container updates ▸ Reactive programming model ▸ WebFlux: reactive HTTP and WebSocket clients / reactive server applications
  5. SPRING 5 WHAT’S NEW? ▸ Core framework revision ▸ Core

    container updates ▸ Reactive programming model ▸ WebFlux: reactive HTTP and WebSocket clients / reactive server applications ▸ Functional, Java 8 Lambda style routing and handling ▸ Testing improvements
  6. SPRING BOOT & KOTLIN APPLICATION CONTEXT @SpringBootApplication class Application fun

    main(args: Array<String>) { SpringApplication.run(Application ::class.java, *args) }
  7. SPRING BOOT & KOTLIN CONTROLLER TEST @RunWith(SpringRunner ::class) @SpringBootTest( classes

    = arrayOf(Application ::class), webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class HelloKotlinControllerTest { @Autowired lateinit var testRestTemplate: TestRestTemplate @Test fun `hello endpoint should return hello world`() { val result = testRestTemplate.getForEntity("/hello", String ::class.java) result.shouldNotBeNull() result.statusCode shouldEqual HttpStatus.OK result.body shouldEqualTo "Hello world" } }
  8. SPRING BOOT & KOTLIN DOMAIN data class GotCharacter(val firstName: String,

    val lastName: String, val age: Int, val actor: String, val id: Long = -1)
  9. SPRING BOOT & KOTLIN DOMAIN @Entity data class GotCharacter(val firstName:

    String, val lastName: String, val age: Int, val actor: String, @Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long = -1)
  10. SPRING BOOT & KOTLIN CONTROLLER @RestController class GotController(private val repository:

    GotCharacterRepository) { @GetMapping("/") fun findAll() = repository.findAll() @GetMapping("/{lastName}") fun findByLastName(@PathVariable lastName: String) = repository.findByLastName(lastName) }
  11. SPRING & KOTLIN NULL-SAFETY OF SPRING FRAMEWORK API ▸ Spring

    5 introduces non-null API declaration for all packages ▸ Explicitly nullable arguments and return values annotated as such ▸ ➜ Spring framework APIs = null-safe from Kotlin side (Kotlin 1.1.50+)
  12. SPRING & KOTLIN NULL-SAFETY OF SPRING FRAMEWORK API ▸ Spring

    5 introduces non-null API declaration for all packages ▸ Explicitly nullable arguments and return values annotated as such ▸ ➜ Spring framework APIs = null-safe from Kotlin side (Kotlin 1.1.50+) @NonNullApi package org.springframework.core; /** * Return the "Cache-Control" header value. * @return {@code null} if no directive was added, or the header value otherwise */ @Nullable public String getHeaderValue()
  13. SPRING & KOTLIN LEVERAGE KOTLIN NULLABLE INFORMATION IN SPRING ANNOTATIONS

    // "GET /foo" and "GET /foo?bar=baz" are allowed @GetMapping("/foo") fun foo(@RequestParam bar: String?) = ... // "GET /foo?bar=baz" is allowed and "GET /foo" will error @GetMapping("/foo") fun foo(@RequestParam bar: String) = ...
  14. SPRING & KOTLIN EXTENSION: REIFIED TYPE PARAMETERS // Java Repo

    repoInfo = restTemplate.getForObject(url, Repo.class); Flux<User> users = client.get().retrieve().bodyToFlux(User.class) // Kotlin val repo: Repo = restTemplate.getForObject(url) val users = client.get().retrieve().bodyToFlux<User>() // or (both are equivalent) val users : Flux<User> = client.get().retrieve().bodyToFlux()
  15. SPRING & KOTLIN EXTENSION: REIFIED TYPE PARAMETERS ResponseEntity<List<Article >> response

    = restTemplate.exchange(uri, HttpMethod.GET, this.httpEntity, new ParameterizedTypeReference<List<Article >>() {}); inline fun <reified T : Any> RestOperations.getForObject(url: String, vararg uriVariables: Any) = getForObject(url, T ::class.java, *uriVariables)
  16. SPRING & KOTLIN REACTOR KOTLIN BUILTIN SUPPORT ▸ Mono, Flux,

    StepVerifier APIs ▸ Kotlin extensions ▸ any class instance to Mono instance with foo.toMono() ▸ create Flux from Java 8 Streams wih stream.toFlux() ▸ extensions for Iterable, CompletableFuture, Throwable
  17. SPRING & KOTLIN KOTLIN-SPRING PLUGIN ▸ Kotlin classes = final

    by default ➜ add open keyword on each class / member functions of Spring beans
  18. SPRING & KOTLIN KOTLIN-SPRING PLUGIN ▸ Kotlin classes = final

    by default ➜ add open keyword on each class / member functions of Spring beans ▸ @Component
 @Async
 @Transactional
 @Cacheable
  19. SPRING & KOTLIN KOTLIN-SPRING PLUGIN ▸ Kotlin classes = final

    by default ➜ add open keyword on each class / member functions of Spring beans ▸ @Component
 @Async
 @Transactional
 @Cacheable ▸ Meta-annotations support: @Configuration, @Controller, @RestController, @Service, @Repository
  20. SPRING & KOTLIN GRADLE: KOTLIN DSL plugins { val kotlinVersion

    = "1.1.50" id("org.jetbrains.kotlin.jvm") version kotlinVersion id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion id("org.jetbrains.kotlin.plugin.noarg") version kotlinVersion id("io.spring.dependency-management") version "1.0.3.RELEASE" } apply { plugin("kotlin") plugin("kotlin-spring") plugin("kotlin-jpa") plugin("org.springframework.boot") } version = "0.0.1-SNAPSHOT" tasks { withType<KotlinCompile> { kotlinOptions { jvmTarget = "1.8" freeCompilerArgs = listOf("-Xjsr305=strict") } } } dependencies { compile("org.jetbrains.kotlin:kotlin-stdlib") compile("org.jetbrains.kotlin:kotlin-reflect") compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-data-jpa") compile("org.springframework.boot:spring-boot-starter-actuator") compile("com.h2database:h2") compile("com.fasterxml.jackson.module:jackson-module-kotlin") testCompile("org.springframework.boot:spring-boot-starter-test") testCompile("org.amshove.kluent:kluent:1.29")
  21. SPRING & KOTLIN KOTLIN SCRIPT BASED TEMPLATES import io.spring.demo.* """

    ${include("header")} <h1>${i18n("title")}</h1> <ul> ${users.joinToLine{ "<li>${i18n("user")} ${it.firstname} ${it.lastname}</li>" }} </ul> ${include("footer")} """
  22. SPRING & KOTLIN WEBFLUX ANNOTATION-BASED @RestController class ReactiveGotController(private val repository:

    ReactiveGotRepository) { @GetMapping("/reactive/characters") fun findAll() = repository.findAll() @GetMapping("/reactive/character/{id}") fun findOne(@PathVariable id: String) = repository.findById(id) @PostMapping("/reactive/character") fun save(@RequestBody character: GotCharacter) = repository.save(character) } interface ReactiveGotRepository { fun findAll(): Flux<GotCharacter> fun findById(id: String): Mono<GotCharacter> fun save(character: GotCharacter): Mono<Void> }
  23. SPRING & KOTLIN WEBFLUX ANNOTATION BASED @RestController class ReactiveGotController(private val

    repository: ReactiveGotRepository) { @GetMapping("/reactive/characters") fun findAll() = repository.findAll() @GetMapping("/reactive/character/{id}") fun findOne(@PathVariable id: String) = repository.findById(id) @PostMapping("/reactive/character") fun save(@RequestBody character: GotCharacter) = repository.save(character) } interface ReactiveGotRepository { fun findAll(): Flux<GotCharacter> fun findById(id: String): Mono<GotCharacter> fun save(character: GotCharacter): Mono<Void> }
  24. SPRING & KOTLIN FUNCTIONAL STYLE // Annotation-based Java @RequestMapping("/quotes/feed", produces

    = TEXT_EVENT_STREAM_VALUE) public Flux<Quote> fetchQuotesStream() { ... }
  25. SPRING & KOTLIN FUNCTIONAL STYLE // Annotation-based Java @RequestMapping("/quotes/feed", produces

    = TEXT_EVENT_STREAM_VALUE) public Flux<Quote> fetchQuotesStream() { ... } // Functional Java without static imports RouterFunctions.route( RequestPredicates.path("/quotes/feed") .and(RequestPredicates.accept(MediaType.TEXT_EVENT_STREAM)), { ... }
  26. SPRING & KOTLIN FUNCTIONAL STYLE // Annotation-based Java @RequestMapping("/quotes/feed", produces

    = TEXT_EVENT_STREAM_VALUE) public Flux<Quote> fetchQuotesStream() { ... } // Functional Java with static imports route( path("/quotes/feed").and(accept(TEXT_EVENT_STREAM)), { ... }
  27. SPRING & KOTLIN FUNCTIONAL STYLE // Annotation-based Java @RequestMapping("/quotes/feed", produces

    = TEXT_EVENT_STREAM_VALUE) public Flux<Quote> fetchQuotesStream() { ... } // Functional Kotlin router { "/quotes/feed" and accept(TEXT_EVENT_STREAM) { ... } }
  28. SPRING & KOTLIN FUNCTIONAL STYLE @Configuration class ApplicationRoutes(val userHandler: UserHandler,

    val blogHandler: BlogHandler, val shopRepository: ShopRepository) { @Bean fun appRouter() = router { GET("/users", userHandler ::fetchAll) GET("/users/{id}", userHandler ::fetch) } @Bean fun nestedRouter() = router { ... } @Bean fun dynamicRouter() = router { ... } }
  29. SPRING & KOTLIN NESTED ROUTING @Bean fun nestedRouter() = router

    { ("/blog" and accept(MediaType.TEXT_HTML)).nest { GET("/", blogHandler ::findAllView) GET("/{slug}", blogHandler ::findOneView) } ("/api/blog" and accept(APPLICATION_JSON)).nest { GET("/", blogHandler ::findAll) GET("/{id}", blogHandler ::findOne) POST("/", blogHandler ::create) } }
  30. SPRING & KOTLIN DYNAMIC ROUTING @Bean fun dynamicRouter() = router

    { shopRepository.findAll() .toIterable() .forEach { shop -> GET("/${shop.id}") { req -> shopHandler.homepage(shop, req) } } }
  31. SPRING & KOTLIN HANDLER @Component class EventHandler(val repository: EventRepository) {

    fun findOne(req: ServerRequest) = ok().json().body(repository.findOne(req.pathVariable("id"))) fun findAll(req: ServerRequest) = ok().json().body(repository.findAll()) }
  32. SPRING & KOTLIN HANDLER @Component class NewsHandler { fun newsView(req:

    ServerRequest) = ok().render("news") fun newsSse(req: ServerRequest) = ok() .contentType(TEXT_EVENT_STREAM) .body(Flux.interval(ofMillis(100)).map { "Hello $it!" }) }