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

  2. import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication … @SpringBootApplication class DemoApplication fun main(args:

    Array<String>) { runApplication<DemoApplication>(*args) } @RestController class MessageResource(val service: MessageService) { @GetMapping fun index(): List<Message> = service.findMessages() @PostMapping fun post(@RequestBody message: Message) { service.post(message) } }
  3. @Service class MessageService(val db: MessageRepository) { fun findMessages(): List<Message> =

    db.findMessages() fun post(message: Message){ db.save(message) } } interface MessageRepository : CrudRepository<Message, String>{ @Query("select * from messages") fun findMessages(): List<Message> } @Table("MESSAGES") data class Message(@Id val id: String?, val text: String)
  4. @Service class MessageService(val db: MessageRepository) { fun findMessages(): List<Message> =

    db.findMessages() fun post(message: Message){ db.save(message) } } interface MessageRepository : CrudRepository<Message, String> { @Query("select * from messages") fun findMessages(): List<Message> } @Table("MESSAGES") data class Message(@Id val id: String?, val text: String)
  5. @Service class MessageService(val db: MessageRepository) { fun findMessages(): List<Message> =

    db.findMessages() fun post(message: Message){ db.save(message) } } interface MessageRepository : CrudRepository<Message, String>{ @Query("select * from messages") fun findMessages(): List<Message> } @Table("MESSAGES") data class Message(@Id val id: String?, val text: String)
  6. @Service class MessageService(val db: MessageRepository) { fun findMessages(): List<Message> =

    db.findMessages() fun post(message: Message){ db.save(message) } } interface MessageRepository : CrudRepository<Message, String>{ @Query("select * from messages") fun findMessages(): List<Message> } @Table("MESSAGES") data class Message(@Id val id: String?, val text: String) Not to be used with JPA!
  7. Important for starters * Kotlin classes are fi nal by

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

    default * Compiler plugins to the rescue! * Data classes don’t play well with JPA
  9. 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
  10. * 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
  11. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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) } }
  12. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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) } }
  13. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id)
  14. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id)
  15. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id)
  16. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id) RowMapper is a functional interface
  17. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id) “SAM conversion” is performed by the compiler
  18. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id)
  19. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", id, { rs, _ -> Message(rs.getString("id"), rs.getString("text")) })
  20. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", id, { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }) vararg is not at the last position
  21. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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<Message> = db.query("select * from messages where id = ?", id, { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }) Lambda expression is at the last position
  22. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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) } }
  23. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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
  24. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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) } }
  25. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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
  26. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = 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) } }
  27. 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()) } }
  28. 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
  29. 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" } }
  30. 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" } }
  31. 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" } }
  32. 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" } }
  33. val beans = beans { bean { CommandLineRunner { println("start

    data initialization .. . ") val repository = ref<MessageRepository>() repository.save(Message(text = "this is the first message!")) repository.save(Message(text = "this is the second } } } @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { this.addInitializers(beans) } }
  34. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { this.addInitializers(beans)

    } } val beans = beans { bean { CommandLineRunner { println("start data initialization .. . ") val repository = ref<MessageRepository>() repository.save(Message(text = "this is the first message!")) repository.save(Message(text = "this is the second } } }
  35. 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"
  36. val person: Person? = getPerson() println(person.firstName) fun getPerson(): Person? {…}

    data class Person( var firstName: String, // … ) Can return null
  37. val person: Person? = getPerson() println(person.firstName) fun getPerson(): Person? {…}

    data class Person( var firstName: String, // … ) Can be null
  38. val person: Person? = getPerson() println(person ?. firstName) fun getPerson():

    Person? {…} data class Person( var firstName: String, // … ) Null-safe dereferencing
  39. val person: Person? = getPerson() println(person !! .firstName) fun getPerson():

    Person? {…} data class Person( var firstName: String, // … ) “I know what I’m doing”
  40. 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