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 & KOTLIN

    View full-size slide

  2. SPRING 5
    WHAT’S NEW?
    ▸ Core framework revision
    ▸ Core container updates
    ▸ Reactive programming model

    View full-size slide

  3. SPRING 5
    WHAT’S NEW?

    View full-size slide

  4. SPRING 5
    REACTOR
    Project Reactor

    View full-size slide

  5. SPRING 5
    REACTIVE STREAMS

    View full-size slide

  6. SPRING 5
    REACTOR
    Project Reactor

    View full-size slide

  7. 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

    View full-size slide

  8. SPRING 5
    REACTIVE STREAMS
    PUBLISHER SUBSCRIBER
    Subscribe then

    request(n) data

    (Backpressure)
    0..N data then

    0..1 (Error | Complete)

    View full-size slide

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

    View full-size slide

  10. SPRING 5
    WEBFLUX

    View full-size slide

  11. 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

    View full-size slide

  12. SPRING 5
    WHAT’S NEW?

    View full-size slide

  13. DEMO
    SPRING KOTLIN

    View full-size slide

  14. SPRING BOOT & KOTLIN
    APPLICATION CONTEXT
    @SpringBootApplication
    class Application
    fun main(args: Array) {
    SpringApplication.run(Application ::class.java, *args)
    }

    View full-size slide

  15. SPRING BOOT & KOTLIN
    CONTROLLER
    @RestController
    class HelloKotlinController {
    @GetMapping("/hello")
    fun hello(): String {
    return "Hello world"
    }
    }

    View full-size slide

  16. 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"
    }
    }

    View full-size slide

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

    View full-size slide

  18. 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)

    View full-size slide

  19. SPRING BOOT & KOTLIN
    REPOSITORY
    interface GotCharacterRepository: CrudRepository {
    fun findByLastName(lastName: String): Iterable
    }

    View full-size slide

  20. 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)
    }

    View full-size slide

  21. KOTLIN SUPPORT:
    SPRING MVC, SPRING WEBFLUX,
    RESTTEMPLATE, SPRING BOOT 2, …
    Spring Framework 5
    TEXT

    View full-size slide

  22. 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+)

    View full-size slide

  23. 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()

    View full-size slide

  24. 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) = ...

    View full-size slide

  25. SPRING & KOTLIN
    EXTENSION: REIFIED TYPE PARAMETERS
    // Java
    Repo repoInfo = restTemplate.getForObject(url, Repo.class);
    Flux users = client.get().retrieve().bodyToFlux(User.class)
    // Kotlin
    val repo: Repo = restTemplate.getForObject(url)
    val users = client.get().retrieve().bodyToFlux()
    // or (both are equivalent)
    val users : Flux = client.get().retrieve().bodyToFlux()

    View full-size slide

  26. SPRING & KOTLIN
    EXTENSION: REIFIED TYPE PARAMETERS
    ResponseEntity> response = restTemplate.exchange(uri, HttpMethod.GET,
    this.httpEntity, new ParameterizedTypeReference>() {});
    inline fun RestOperations.getForObject(url: String, vararg uriVariables: Any) =
    getForObject(url, T ::class.java, *uriVariables)

    View full-size slide

  27. 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

    View full-size slide

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

    View full-size slide

  29. 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

    View full-size slide

  30. 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

    View full-size slide

  31. 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 {
    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")

    View full-size slide

  32. SPRING & KOTLIN
    KOTLIN SCRIPT BASED TEMPLATES
    import io.spring.demo.*
    """
    ${include("header")}
    ${i18n("title")}

    ${users.joinToLine{ "${i18n("user")} ${it.firstname} ${it.lastname}" }}

    ${include("footer")}
    """

    View full-size slide

  33. SPRING 5
    WEBFLUX

    View full-size slide

  34. 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
    fun findById(id: String): Mono
    fun save(character: GotCharacter): Mono
    }

    View full-size slide

  35. 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
    fun findById(id: String): Mono
    fun save(character: GotCharacter): Mono
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. SPRING & KOTLIN
    FUNCTIONAL STYLE
    // Annotation-based Java
    @RequestMapping("/quotes/feed", produces = TEXT_EVENT_STREAM_VALUE)
    public Flux fetchQuotesStream() { ... }
    // Functional Kotlin
    router {
    "/quotes/feed" and accept(TEXT_EVENT_STREAM) { ... }
    }

    View full-size slide

  40. 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 {
    ...
    }
    }

    View full-size slide

  41. 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)
    }
    }

    View full-size slide

  42. SPRING & KOTLIN
    DYNAMIC ROUTING
    @Bean
    fun dynamicRouter() = router {
    shopRepository.findAll()
    .toIterable()
    .forEach { shop ->
    GET("/${shop.id}") {
    req ->
    shopHandler.homepage(shop, req)
    } }
    }

    View full-size slide

  43. 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())
    }

    View full-size slide

  44. 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!" })
    }

    View full-size slide

  45. SPRING 5
    ❤️

    View full-size slide