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 Slide

  2. View Slide

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

    View Slide

  4. SPRING 5
    WHAT’S NEW?

    View Slide

  5. SPRING 5
    REACTOR
    Project Reactor

    View Slide

  6. SPRING 5
    REACTIVE STREAMS

    View Slide

  7. SPRING 5
    REACTOR
    Project Reactor

    View Slide

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

  9. SPRING 5
    REACTIVE STREAMS
    PUBLISHER SUBSCRIBER
    Subscribe then

    request(n) data

    (Backpressure)
    0..N data then

    0..1 (Error | Complete)

    View Slide

  10. View Slide

  11. View Slide

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

    View Slide

  13. SPRING 5
    WEBFLUX

    View Slide

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

  15. SPRING 5
    WHAT’S NEW?

    View Slide

  16. View Slide

  17. DEMO
    SPRING KOTLIN

    View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. View Slide

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

    View Slide

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

    View Slide

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

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

    View Slide

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

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

    View Slide

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

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

    View Slide

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

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

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

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

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

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

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

    View Slide

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

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

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

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

  41. SPRING 5
    WEBFLUX

    View Slide

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

  43. TEXT
    WEBFLUX

    View Slide

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

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

    View Slide

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

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

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

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

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

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

    View Slide

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

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

  54. SPRING 5
    ❤️

    View Slide