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

SpringIO 2022 - Spring extensions for Kotlin

SpringIO 2022 - Spring extensions for Kotlin

Anton Arhipov

June 10, 2022
Tweet

More Decks by Anton Arhipov

Other Decks in Programming

Transcript

  1. @antonarhipov
    Spring extensions for Kotlin

    View full-size slide

  2. @antonarhipov

    View full-size slide

  3. import org.springframework.boot.autoconfigure.SpringBootApplication


    import org.springframework.boot.runApplication





    @SpringBootApplication


    class DemoApplication


    fun main(args: Array) {


    runApplication(*args)


    }


    View full-size slide

  4. import org.springframework.boot.autoconfigure.SpringBootApplication


    import org.springframework.boot.runApplication





    @SpringBootApplication


    class DemoApplication


    fun main(args: Array) {


    runApplication(*args)


    }


    View full-size slide

  5. import org.springframework.boot.autoconfigure.SpringBootApplication


    import org.springframework.boot.runApplication





    @SpringBootApplication


    class DemoApplication


    fun main(args: Array) {


    runApplication(*args)


    }


    View full-size slide

  6. import org.springframework.boot.autoconfigure.SpringBootApplication


    import org.springframework.boot.runApplication





    @SpringBootApplication


    class DemoApplication


    fun main(args: Array) {


    runApplication(*args)


    }


    @RestController


    class MessageResource(val service: MessageService) {


    @GetMapping


    fun index(): List = service.findMessages()


    @PostMapping


    fun post(@RequestBody message: Message) {


    service.post(message)


    }


    }


    View full-size slide

  7. @Service


    class MessageService(val db: MessageRepository) {


    fun findMessages(): List = db.findMessages()


    fun post(message: Message){


    db.save(message)


    }


    }


    interface MessageRepository : CrudRepository{


    @Query("select * from messages")


    fun findMessages(): List


    }


    @Table("MESSAGES")


    data class Message(@Id val id: String?, val text: String)


    View full-size slide

  8. @Service


    class MessageService(val db: MessageRepository) {


    fun findMessages(): List = db.findMessages()


    fun post(message: Message){


    db.save(message)


    }


    }


    interface MessageRepository : CrudRepository {


    @Query("select * from messages")


    fun findMessages(): List


    }


    @Table("MESSAGES")


    data class Message(@Id val id: String?, val text: String)


    View full-size slide

  9. @Service


    class MessageService(val db: MessageRepository) {


    fun findMessages(): List = db.findMessages()


    fun post(message: Message){


    db.save(message)


    }


    }


    interface MessageRepository : CrudRepository{


    @Query("select * from messages")


    fun findMessages(): List


    }


    @Table("MESSAGES")


    data class Message(@Id val id: String?, val text: String)


    View full-size slide

  10. @Service


    class MessageService(val db: MessageRepository) {


    fun findMessages(): List = db.findMessages()


    fun post(message: Message){


    db.save(message)


    }


    }


    interface MessageRepository : CrudRepository{


    @Query("select * from messages")


    fun findMessages(): List


    }


    @Table("MESSAGES")


    data class Message(@Id val id: String?, val text: String)


    Not to be used with JPA!

    View full-size slide

  11. https://stackover
    fl
    ow.com/questions/58127353/should-i-use-kotlin-data-class-as-jpa-entity
    Resources:
    https://kotlinexpertise.com/hibernate-with-kotlin-spring-boot/
    https://dzone.com/articles/kotlin-data-classes-and-jpa
    https://blog.codecentric.de/en/2017/06/kotlin-spring-working-jpa-data-classes/
    https://spring.io/guides/tutorials/spring-boot-kotlin/

    View full-size slide

  12. Important for starters

    View full-size slide

  13. Important for starters
    * Kotlin classes are
    fi
    nal by default

    View full-size slide

  14. Important for starters
    * Kotlin classes are
    fi
    nal by default
    * Compiler plugins to the rescue!

    View full-size slide

  15. Important for starters
    * Kotlin classes are
    fi
    nal by default
    * Compiler plugins to the rescue!
    * Data classes don’t play well with JPA

    View full-size slide

  16. Important for starters
    * Kotlin classes are
    fi
    nal by default
    * Compiler plugins to the rescue!
    * Data classes don’t play well with JPA
    * Use normal classes with JPA

    View full-size slide

  17. * Kotlin classes are
    fi
    nal by default
    * Compiler plugins to the rescue!
    * Data classes don’t play well with JPA
    * Use normal classes with JPA
    Important for starters
    * Use start.spring.io to generate con
    fi
    gs

    View full-size slide

  18. kotlin extensions

    View full-size slide

  19. println("hello".uuid())


    View full-size slide

  20. fun String.uuid(): String =


    UUID.nameUUIDFromBytes(this.encodeToByteArray()).toString()


    println("hello".uuid())


    View full-size slide

  21. fun String.uuid(): String =


    UUID.nameUUIDFromBytes(encodeToByteArray()).toString()


    println("hello".uuid())


    View full-size slide

  22. fun String.uuid() =


    UUID.nameUUIDFromBytes(encodeToByteArray()).toString()


    println("hello".uuid())


    View full-size slide

  23. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    View full-size slide

  24. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    View full-size slide

  25. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    RowMapper { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) },


    id)

    View full-size slide

  26. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    RowMapper { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) },


    id)

    View full-size slide

  27. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    RowMapper { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) },


    id)

    View full-size slide

  28. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    RowMapper { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) },


    id)
    RowMapper is a
    functional interface

    View full-size slide

  29. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) },


    id)
    “SAM conversion” is
    performed by the compiler

    View full-size slide

  30. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) },


    id)

    View full-size slide

  31. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    id,


    { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) })

    View full-size slide

  32. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    id,


    { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) })
    vararg is not at the last position

    View full-size slide

  33. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?",


    id,


    { rs, _
    ->
    Message(rs.getString("id"), rs.getString("text")) })
    Lambda expression is at the last position

    View full-size slide

  34. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    View full-size slide

  35. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    Passing trailing lambda as a
    parameter to the function

    View full-size slide

  36. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    View full-size slide

  37. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    This is an extension function


    For the existing Java class in the
    Spring framework

    View full-size slide

  38. @Service


    class MessageService(val db: JdbcTemplate) {


    fun findMessages(): List = db.query("select * from messages") { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun findMessageById(id: String): List =


    db.query("select * from messages where id = ?", id) { rs, _
    - >

    Message(rs.getString("id"), rs.getString("text"))


    }


    fun post(message: Message){


    db.update("insert into messages values ( ?, ? )",


    message.id
    ?:
    message.text.uuid(), message.text)


    }


    }


    View full-size slide

  39. extensions


    & Trailing lambdas

    View full-size slide

  40. fun String.uuid() =


    UUID.nameUUIDFromBytes(encodeToByteArray()).toString()


    View full-size slide

  41. fun String.uuid() =


    UUID.nameUUIDFromBytes(encodeToByteArray()).toString()


    fun applyAction(vararg s: String, action: (String)
    ->
    Unit) {


    s.forEach(action)


    }


    View full-size slide

  42. fun String.uuid() =


    UUID.nameUUIDFromBytes(encodeToByteArray()).toString()


    fun applyAction(vararg s: String, action: (String)
    ->
    Unit) {


    s.forEach(action)


    }


    fun main() {


    applyAction("hello", "bye") { s: String
    ->

    println(s.uuid())


    }


    }


    View full-size slide

  43. fun String.uuid() =


    UUID.nameUUIDFromBytes(encodeToByteArray()).toString()


    fun applyAction(vararg s: String, action: (String)
    ->
    Unit) {


    s.forEach(action)


    }


    fun main() {


    applyAction("hello", "bye") { s: String
    ->

    println(s.uuid())


    }


    }


    Note to self: open the IDE

    View full-size slide

  44. more kotlin extensions


    In spring

    View full-size slide

  45. class HtmlController(val messageService: MessageService) {


    @GetMapping("/")


    fun index(model: Model): String {


    val messages = messageService.latest()


    model.asMap()["messages"] = messages


    model.asMap()["lastMessageId"] = messages.lastOrNull()
    ?
    .
    id
    ?
    :
    ""


    return "chat"


    }


    }


    View full-size slide

  46. class HtmlController(val messageService: MessageService) {


    @GetMapping("/")


    fun index(model: Model): String {


    val messages = messageService.latest()


    model.asMap()["messages"] = messages


    model.asMap()["lastMessageId"] = messages.lastOrNull()
    ?
    .
    id
    ?
    :
    ""


    return "chat"


    }


    }


    View full-size slide

  47. class HtmlController(val messageService: MessageService) {


    @GetMapping("/")


    fun index(model: Model): String {


    val messages = messageService.latest()


    model.asMap()["messages"] = messages


    model.asMap()["lastMessageId"] = messages.lastOrNull()
    ?
    .
    id
    ?
    :
    ""


    return "chat"


    }


    }


    View full-size slide

  48. class HtmlController(val messageService: MessageService) {


    @GetMapping("/")


    fun index(model: Model): String {


    val messages = messageService.latest()


    model["messages"] = messages


    model["lastMessageId"] = messages.lastOrNull()
    ?.
    id
    ?:
    ""


    return "chat"


    }


    }


    View full-size slide

  49. Type-safe builders
    A.k.a. dsl

    View full-size slide

  50. @SpringBootApplication


    class DemoApplication


    fun main(args: Array) {


    runApplication(*args) {


    addInitializers(beans)


    }


    }


    View full-size slide

  51. @SpringBootApplication


    class DemoApplication


    fun main(args: Array) {


    runApplication(*args) {


    this.addInitializers(beans)


    }


    }


    “this” refers to SpringApplication instance

    View full-size slide

  52. val beans = beans {


    bean {


    CommandLineRunner {


    println("start data initialization
    .. .
    ")


    val repository = ref()


    repository.save(Message(text = "this is the first message!"))


    repository.save(Message(text = "this is the second


    }


    }


    }
    @SpringBootApplication


    class DemoApplication


    fun main(args: Array) {


    runApplication(*args) {


    this.addInitializers(beans)


    }


    }


    View full-size slide

  53. @SpringBootApplication


    class DemoApplication


    fun main(args: Array) {


    runApplication(*args) {


    this.addInitializers(beans)


    }


    }


    val beans = beans {


    bean {


    CommandLineRunner {


    println("start data initialization
    .. .
    ")


    val repository = ref()


    repository.save(Message(text = "this is the first message!"))


    repository.save(Message(text = "this is the second


    }


    }


    }

    View full-size slide

  54. Trailing lambdas + (extension) functions +


    named parameters + lambda w/ receiver = DSL
    fun foo(f: ()
    ->
    Unit) {}


    fun bar(f: ()
    ->
    Unit) {}


    fun baz(f: ()
    ->
    Unit) {}


    fun blah(id: Int, f: Klazz.()
    ->
    Unit) {}


    class Klazz(var text: String)


    fun Klazz.hello() : String = "Hello"


    View full-size slide

  55. val person: Person? = getPerson()


    println(person.firstName)


    fun getPerson(): Person? {…}


    data class Person(


    var firstName: String,


    //



    )


    View full-size slide

  56. val person: Person? = getPerson()


    println(person.firstName)


    fun getPerson(): Person? {…}


    data class Person(


    var firstName: String,


    //



    )


    Can return null

    View full-size slide

  57. val person: Person? = getPerson()


    println(person.firstName)


    fun getPerson(): Person? {…}


    data class Person(


    var firstName: String,


    //



    )


    Can be null

    View full-size slide

  58. val person: Person? = getPerson()


    println(person
    ?.
    firstName)


    fun getPerson(): Person? {…}


    data class Person(


    var firstName: String,


    //



    )


    Null-safe dereferencing

    View full-size slide

  59. val person: Person? = getPerson()


    println(person
    !!
    .firstName)


    fun getPerson(): Person? {…}


    data class Person(


    var firstName: String,


    //



    )


    “I know what I’m doing”

    View full-size slide

  60. Can be null
    Should not be null

    View full-size slide

  61. build.gradle.kts

    View full-size slide

  62. Kotlin features you have seen now
    Type inference


    Inline functions


    Data classes


    Rei
    fi
    ed generics


    Top-level functions


    Named arguments
    Operator overloading


    Trailing lambdas


    Extension functions


    Functional literal with receiver


    Nullable types


    Support for JSR-305

    View full-size slide

  63. Kuestions?
    speakerdeck.com/antonarhipov
    @antonarhipov

    View full-size slide