Add kotlin to java project

Из Java в Kotlin: туда и обратно

В статье рассмотрены проблемы и решения, которые возникли при добавлении Kotlin в существующий небольшой микросервис на Spring Boot, написанный изначально на Java. В рамках статьи не будут рассматриваться плюсы и минусы того или иного языка — здесь и так сломано много копий (некоторые статьи на habr: 1, 2, 3) и, как мне кажется, каждая команда должна это решать для себя. Рассматривается стандартный стек Spring WebMVC (не реактивный)

Краткое описание микросервиса, с которым будем работать

До перевода на Kotlin использовался такой стек:

  • Java 11
  • Spring Web MVC (в рамках Spring Boot)
  • Spring Data JPA
  • Map Struct
  • Lombook
  • Maven

С чего начать

Переводим тесты на Kotlin

Первая рекомендация, которую вы найдете, — начинайте с тестов. В рамках своего проекта — подтверждаем, что это самый правильный путь. Пока вы будете добавлять тесты на Kotlin в проект, вы пройдете через следующие этапы:

  1. Настроите сборку совместно с Kotlin
  2. Добавите требуемые библиотеки
  3. Поймаете основные ошибки
  4. Наконец-то напишите тесты на давно заброшенные участки кода

И при этом основной код, отвечающий за бизнес-логику затронут не будет. Начать с тестов нам очень помогло, даже хотя бы тем, что поймали пару ошибок в нашей существующей бизнес-логике.

Подключаем Kotlin в проект

Несколько слов о Gradle Kotlin DSL

Дополнительно к стандартному набору для сборки пакетов (Maven и Gradle на Groovy) теперь добавился еще один — Gradle Kotlin DSL. Это может быть хорошим вариантом для тех, кто хотел бы использовать Gradle, но смущала необходимость использовать Groovy.

Читайте также:  Sql to java online

Из плюсов данного решения — более корректные подсказки в IDEA (хотя, если честно, IDEA сильно тормозит при анализе build.gradle.kt файлов). Одним из главных неудобств, как мне кажется, является то, что не все возможности Gradle на Groovy реализованы или реализованы не зеркально, и поэтому многие подсказки со stackoverflow не будут работать. Поэтому мы остались на привычном нам Maven.

Maven

Если вы используете стек Spring-boot, то рекомендую для начала создать тестовый проект с нуля через https://start.spring.io/.

Ниже приведены 2 pom.xml файла, которые создает https://start.spring.io/ для Java и Kotlin.

  4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.5 com.example demo-java 0.0.1-SNAPSHOT demo-java demo-java 11   org.springframework.boot spring-boot-starter-data-jpa  org.springframework.boot spring-boot-starter-web  org.liquibase liquibase-core  com.h2database h2 runtime  org.projectlombok lombok true  org.springframework.boot spring-boot-starter-test test     org.springframework.boot spring-boot-maven-plugin   org.projectlombok lombok       
  4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.5 com.example demo-kotlin 0.0.1-SNAPSHOT demo-kotlin demo-kotlin 11 1.5.31   org.springframework.boot spring-boot-starter-data-jpa  org.springframework.boot spring-boot-starter-web  com.fasterxml.jackson.module jackson-module-kotlin  org.jetbrains.kotlin kotlin-reflect  org.jetbrains.kotlin kotlin-stdlib-jdk8  org.liquibase liquibase-core  com.h2database h2 runtime  org.projectlombok lombok true  org.springframework.boot spring-boot-starter-test test   $/src/main/kotlin $/src/test/kotlin  org.springframework.boot spring-boot-maven-plugin   org.projectlombok lombok     org.jetbrains.kotlin kotlin-maven-plugin  -Xjsr305=strict  spring jpa    org.jetbrains.kotlin kotlin-maven-allopen $ org.jetbrains.kotlin kotlin-maven-noarg $     

Давайте рассмотрим, в чем разница с Java проектом по мнению https://start.spring.io/:

 com.fasterxml.jackson.module jackson-module-kotlin  org.jetbrains.kotlin kotlin-reflect  org.jetbrains.kotlin kotlin-stdlib-jdk8 
  $/src/main/kotlin $/src/test/kotlin . org.jetbrains.kotlin kotlin-maven-plugin  -Xjsr305=strict  spring jpa    org.jetbrains.kotlin kotlin-maven-allopen $ org.jetbrains.kotlin kotlin-maven-noarg $    

Подробнее про kotlin-maven-plugin можно прочитать здесь.

Данная конфигурация подойдет только, если у вас в проекте не будет кода на Java. Если вы хотите использовать оба языка (что и происходит при миграции), то нужно добавить и корректно настроить maven-compiler-plugin. (Пример можно посмотреть здесь или здесь)

  . org.jetbrains.kotlin kotlin-maven-plugin  compile compile   $/src/main/kotlin $/src/main/java    test-compile test-compile   $/src/test/kotlin $/src/test/java      -Xjsr305=strict  spring jpa    org.jetbrains.kotlin kotlin-maven-allopen $ org.jetbrains.kotlin kotlin-maven-noarg $   org.apache.maven.plugins maven-compiler-plugin   default-compile none   default-testCompile none  java-compile compile compile   java-test-compile test-compile testCompile       

-Xjsr305=strict позволяет Kotlin корректно использоваться nullable-типы для некоторых api Spring (ссылка)

Про плагины All-open и No-arg будет подробнее рассказано нижe.

Kotlin plugins

Для изменения процесса компиляции в Kotlin используются compiler plugins.

По умолчанию spring-initializer добавляет 2 плагина: all-open и no-arg. Так же часто используются kapt и плагин для Lombok.

all-open

По умолчанию все классы и их члены в Kotlin имеют неявный модификатор final и, соответственно, они не могут быть наследованы и переопределены, что очень сильно ограничивает их использование в Spring. Чтобы решить эту проблему, используется plugin all-open (документация), который добавляет open к указанным аннотациями классам и членам классов. Для работы со spring используется уже преднастроенный plugin kotlin-spring.

Kotlin-spring работает со следующими аннотациями:

Если вы делаете свою аннотацию, то возможно вам стоит добавить plugin kotlin-allopen и настроить его для работы с этой аннотацией.

Обратите внимание, что в списке аннотаций нет аннотаций, относящихся к JPA и их нужно добавлять отдельно (см. статью от Haulmont).

No-arg

Плагин добавляет пустой конструктор к указанным аннотациями классам. Для jpa есть преднастроенный kotlin-jpa плагин. Он работает со следующими аннотациями: @Entity , @Embeddable , и @MappedSuperclass . При этом он не делает эти классы open , это требуется сделать руками с помощью плагина kotlin-allopen.

Kapt

Является адаптером для annotation processors (документация). Используется, например, mapstruct для генерации кода для маперов на kotlin

Пример добавления mapstruct для Kotlin

 org.jetbrains.kotlin kotlin-maven-plugin $  kapt kapt   src/main/kotlin src/main/java   org.mapstruct mapstruct-processor $    . . 

По опыту использования могу сказать, что довольно часто выдает ошибки и ломает сборку, особенно, если в проекте есть Lombok.

Плагин позволяет корректно вызывать сгенерированные lombok методы из kotlin кода. (Документация). Плагин находится еще в бете, и не поддерживает @Builder .

Работа со Spring

Spring уже хорошо оптимизирован для работы с Kotlin.

По выступлениям можно посмотреть, как и что добавлялось для поддержки Kotlin:

В этой части статьи приведены небольшие рекомендации и различия по использования Kotlin со Spring по сранению с Java.

  • Так как точка запуска в Kotlin является функция main, то запуск Spring-Boot приложения теперь выглядит так:
@SpringBootApplication class DemoKotlinApplication fun main(args: Array) < runApplication(*args) >
  • В Spring добавлены и добавляются функции расширения для удобства использования Kotlin, например, RestOperationsExtensions.kt. Поэтому стоит поискать новое API, адаптированное для Kotlin — возможно оно уже есть
  • Для Kotlin для внедрения зависимостей рекомендуется использовать val аргументы в конструкторе,
@Component class YourBean( private val mongoTemplate: MongoTemplate, private val solrClient: SolrClient )

Но возможны и другие варианты.

Если требуется внедрить зависимость для поля, то можно использовать latenit var

Эта запись эквивалентна такой записи в Java:

@Component public class YourBean

Можно использовать инъекцию и через set-методы, но при этом поле становится Nullable

 var hello: HelloService? = null @Autowired set(value)
  • Для конфигурации свойств требуется экранировать $: @Value(«\$»)
  • Поддерживается создания классов конфигураций через конструктор при использовании @ConstructorBinding
@ConfigurationProperties("test") @ConstructorBinding class TestConfig( val name:String ) 

Так же можно создавать через lateinit var

@ConfigurationProperties("test") class TestConfig
  • Для генерации мета-информации нужно настроить spring-boot-configuration-processor для работы с kapt

Работа с Hibernate

Основные ошибки и проблемы хорошо описаны в статье Haulmont.

Еще раз обращу внимание на необходимость корректной настройки плагинов no-args и all-open и на корректную реализацию методов hashCode и equals.

Остальные библиотеки

Jackson

Spring использует Jackson для сериализации/десериализации данных. Для работы с Kotlin требуется добавить зависимость Jackson Module Kotlin. После этого вы сможете работать с Kotlin-специфичными типами. При этом нет необходимости явно указывать типа объектов.

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue data class MyStateObject(val name: String, val age: Int) . val mapper = jacksonObjectMapper() val state = mapper.readValue(json) // or val state: MyStateObject = mapper.readValue(json) // or myMemberWithType = mapper.readValue(json)

MapStruct

MapStruct работает через annotation processor. Поэтому для его работы необходимо корректно настроить kapt. При этом если модели используют аннотации Lombok, то при генерации мапперов будут проблемы (см. Lombok)

 org.jetbrains.kotlin kotlin-maven-plugin $  kapt kapt   src/main/kotlin src/main/java   org.mapstruct mapstruct-processor $    . . 

Lombok

Работа с Lombok-методами напрямую из Kotlin из коробки не работает. Для этого требуется использовать Lombok compiler plugin.

При этом возникают большие проблемы при совместной работе Kapt и Lombok. По умолчанию kapt начинает запускать все annotation processors и отключает их работу через javac. Для того чтобы Lombok продолжал работать, требуется явно это указать

 org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8  org.projectlombok lombok $   

При этом Lombok будет корректно работать с kapt только, если annotation processor, который используют kapt, не зависит от Lombok. В нашем случае это было не так, так как мы используем MapStruct, и поэтому пришлось в первую очередь всю доменную модель переводить на Kotlin. Так же одним из вариантов является использование Delombok. Эта проблема известная и уже поднималась на habr.

Mockito

Mockito некорректно работает с типами Kotlin из коробки. Поэтому Spring рекомендует использовать Mockk. Также для Mockito появился специальный модуль, добавляющий поддержку Kotlin — Mockito-Kotlin.

В нашем проекте мы использовали Mockito-Kotlin. Единственная проблема в нем, что нужно аккуратно следить, откуда что импортируется, так как, например, any() теперь будут в 2 местах — org.mockito.kotlin и org.mockito.Mockito

Логирование

При разработке enterprise-приложений привыкаешь ставить аннотацию @Slf4j от Lombok и получать готовый логгер. Так как Lombok с Kotlin не дружит, то приходится искать другие пути.

Можно пойти по пути создания своей надстройки — как в этой статье. Но для нас оказалась очень удобной библиотека — kotlin-logging, которая предоставляет возможность делать, например, вот такие вещи:

import mu.KotlinLogging private val logger = KotlinLogging.logger <> class FooWithLogging < val message = "world" fun bar() < logger.debug < "hello $message" >> >

Как видно, нам здесь не нужно указывать явно класс и пакет. А также поддерживается ленивое создание сообщения.

Выводы

Хочется закончить статью краткими выводами. Использование Java и Kotlin совместно в одном проекте требует дополнительныx настроек, но практически все решается и мы получаем возможность использовать 2 языка в одном проекте. Самой большой проблемой для нас оказалось невозможность полностью подружить Lombok и Kotlin.

Kotlin приносит много полезных идей. Но, как мне кажется, полностью удобство Kotlin начинаешь ценить при разработке реактивного кода. В данной статье это не рассмотрено, но это хорошо показано, например, в этой статье.

Использованные материалы

Источник

Оцените статью