В статье рассмотрены проблемы и решения, которые возникли при добавлении 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.

Из плюсов данного решения - более корректные подсказки в 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.

pom.xml для Java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-java</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-java</name>
    <description>demo-java</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
pom.xml для Kotlin
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-kotlin</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-kotlin</name>
    <description>demo-kotlin</description>
    <properties>
        <java.version>11</java.version>
        <kotlin.version>1.5.31</kotlin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-kotlin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-jdk8</artifactId>
        </dependency>
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <configuration>
                    <args>
                        <arg>-Xjsr305=strict</arg>
                    </args>
                    <compilerPlugins>
                        <plugin>spring</plugin>
                        <plugin>jpa</plugin>
                    </compilerPlugins>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-noarg</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>

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

  • Добавилась версия Kotlin

<properties>
 ....
  <kotlin.version>1.5.31</kotlin.version>
</properties>
  • Добавились 2 стандартных библиотеки для работы с Kotlin и библиотека для Jackson (про нее - ниже)

<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
  • В раздел build добавился раздел про Kotlin

    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
        <plugins>
          .....
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <configuration>
                    <args>
                        <arg>-Xjsr305=strict</arg>
                    </args>
                    <compilerPlugins>
                        <plugin>spring</plugin>
                        <plugin>jpa</plugin>
                    </compilerPlugins>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-noarg</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

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

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

Пример для Kotlin и Java
<build>
  <plugins>
    .....
    <plugin>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-maven-plugin</artifactId>
      <executions>
        <execution>
          <id>compile</id>
          <goals>
            <goal>compile</goal>
          </goals>
          <configuration>
            <sourceDirs>
              <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
              <sourceDir>${project.basedir}/src/main/java</sourceDir>
            </sourceDirs>
          </configuration>
        </execution>
        <execution>
          <id>test-compile</id>
          <goals> <goal>test-compile</goal> </goals>
          <configuration>
            <sourceDirs>
              <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
              <sourceDir>${project.basedir}/src/test/java</sourceDir>
            </sourceDirs>
          </configuration>
        </execution>
      </executions>
      <configuration>
        <args>
          <arg>-Xjsr305=strict</arg>
        </args>
        <compilerPlugins>
          <plugin>spring</plugin>
          <plugin>jpa</plugin>
        </compilerPlugins>
      </configuration>
      <dependencies>
        <dependency>
          <groupId>org.jetbrains.kotlin</groupId>
          <artifactId>kotlin-maven-allopen</artifactId>
          <version>${kotlin.version}</version>
        </dependency>
        <dependency>
          <groupId>org.jetbrains.kotlin</groupId>
          <artifactId>kotlin-maven-noarg</artifactId>
          <version>${kotlin.version}</version>
        </dependency>
      </dependencies>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <executions>
        <!-- Replacing default-compile as it is treated specially by maven -->
        <execution>
          <id>default-compile</id>
          <phase>none</phase>
        </execution>
        <!-- Replacing default-testCompile as it is treated specially by maven -->
        <execution>
          <id>default-testCompile</id>
          <phase>none</phase>
        </execution>
        <execution>
          <id>java-compile</id>
          <phase>compile</phase>
          <goals>
            <goal>compile</goal>
          </goals>
        </execution>
        <execution>
          <id>java-test-compile</id>
          <phase>test-compile</phase>
          <goals>
            <goal>testCompile</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

-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 работает со следующими аннотациями:

  • @Component

  • @Async

  • @Transactional

  • @Cacheable

  • @SpringBootTest

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

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

No-arg

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

Kapt

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

Пример добавления mapstruct для Kotlin
<plugin>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-maven-plugin</artifactId>
  <version>${kotlin.version}</version>
  <executions>
    <execution>
        <id>kapt</id>
        <goals>
            <goal>kapt</goal>
        </goals>
        <configuration>
            <sourceDirs>
                <sourceDir>src/main/kotlin</sourceDir>
                <sourceDir>src/main/java</sourceDir>
            </sourceDirs>
            <annotationProcessorPaths>
                <annotationProcessorPath>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct-processor</artifactId>
                    <version>${mapstruct.version}</version>
                </annotationProcessorPath>
            </annotationProcessorPaths>
        </configuration>
    </execution>
  ...
  </executions>
  ....
</plugin>

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

Lombok

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

Работа со Spring

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

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

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

  • Так как точка запуска в Kotlin является функция main, то запуск Spring-Boot приложения теперь выглядит так:

@SpringBootApplication
class DemoKotlinApplication

fun main(args: Array<String>) {
    runApplication<DemoKotlinApplication>(*args)
}
  • В Spring добавлены и добавляются функции расширения для удобства использования Kotlin, например, RestOperationsExtensions.kt. Поэтому стоит поискать новое API, адаптированное для Kotlin - возможно оно уже есть

  • Для Kotlin для внедрения зависимостей рекомендуется использовать val аргументы в конструкторе,

@Component
class YourBean(
  private val mongoTemplate: MongoTemplate, 
  private val solrClient: SolrClient
)

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

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

@Component
class YourBean {

    @Autowired
    lateinit var mongoTemplate: MongoTemplate

    @Autowired
    lateinit var solrClient: SolrClient
}

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

@Component
public class YourBean {
   @Autowired
   public MongoTemplate mongoTemplate;
   @Autowired
   public SolrClient solrClient;
}

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

    var hello: HelloService? = null
        @Autowired
        set(value) {
            field = value
            println("test")
        }
  • Для конфигурации свойств требуется экранировать $: @Value("\${property}")

  • Поддерживается создания классов конфигураций через конструктор при использовании @ConstructorBinding

@ConfigurationProperties("test")
@ConstructorBinding
class TestConfig(
    val name:String
) 

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

@ConfigurationProperties("test")
class TestConfig {
    lateinit var name: String
}
  • Для генерации мета-информации нужно настроить 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<MyStateObject>(json)
// or
val state: MyStateObject = mapper.readValue(json)
// or
myMemberWithType = mapper.readValue(json)

MapStruct

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

<plugin>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-maven-plugin</artifactId>
  <version>${kotlin.version}</version>
  <executions>
    <execution>
        <id>kapt</id>
        <goals>
            <goal>kapt</goal>
        </goals>
        <configuration>
            <sourceDirs>
                <sourceDir>src/main/kotlin</sourceDir>
                <sourceDir>src/main/java</sourceDir>
            </sourceDirs>
            <annotationProcessorPaths>
                <annotationProcessorPath>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct-processor</artifactId>
                    <version>${mapstruct.version}</version>
                </annotationProcessorPath>
            </annotationProcessorPaths>
        </configuration>
    </execution>
  ...
  </executions>
  ....
</plugin>

Lombok

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

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

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
            <annotationProcessorPath>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </annotationProcessorPath>
        </annotationProcessorPaths>
    </configuration>
</plugin>

При этом 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 начинаешь ценить при разработке реактивного кода. В данной статье это не рассмотрено, но это хорошо показано, например, в этой статье.

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

Комментарии (24)


  1. jreznot
    24.10.2021 18:36
    +4

    А зачем вам Lombok в Kotlin проектах?


    1. tbl
      24.10.2021 20:38
      +3

      Если проект имеет давнюю историю, то под котлин его можно перевозить по частям. Тогда для частей, написанных на java и использующих ломбок, нужна либо деломбокизация, либо заставить дружить ломбок с котлином при сборке проекта


      1. Borz
        24.10.2021 20:56
        +1

        либо разделить сборку на 2 этапа - сперва ту часть кода, в которой Lombok, а потом всё остальное - мы в своё время пошли именно по такому пути


    1. pyltsinm Автор
      24.10.2021 21:01

      Уже ответили аналогично. Переводили на котлин по частям. Когда наткнулись на эту особенность работы с Lombok - скорректировали порядок перевода, чтобы мухи отдельно, слоны отдельно


  1. eleoleeye
    24.10.2021 21:02
    +2

    А какой профит мы получаем?


    1. pyltsinm Автор
      24.10.2021 21:04
      +3

      Меньше ошибок с null в новом коде, более богатая стандартная библиотека, выразительный язык. Нас больше интересовали подводные камни. Спорить о том какой язык лучше или хуже - можно очень долго)


      1. Throwable
        24.10.2021 21:23
        -2

        Вопреки заявленному разработчиками, интеграция с джавой и ее экосистемой далеко не бесшовная. Цена за "удобство" -- общее усложнение билда (соответственно увеличивающаяся "хрупкость"), наличие подводных камней и лишнего геморроя практически с любой библиотекой. К слову, null -- это одна из самых распространенных, но наиболее легко отлаживаемых ошибок. Поэтому формальный смысл данной затеи на мой взгляд очень сомнителен. Котлин оптимально использовать в "чистом" проекте по возможности с нативной котлиновской экосистемой (пусть и небольшой), либо с джавовыми библиотеками, где заявлена поддержка Kotlin.


        1. UScorp
          25.10.2021 12:47
          +5

          null - легко выявляется на продакшене, а при разработке можно и пропустить за цепочкой вызовов методов/свойств или просто по лени с мыслью "да, там null, никогда не будет, я в этом уверен".

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

          Нативной jave бы перенять эту работу с null-типами, было бы здорово.


      1. amarkevich
        26.10.2021 14:51

        привычка объявлять переменные final сводит появление null к минимуму


    1. igorhak
      26.10.2021 11:55

      Как минимум писать меньше кода, как сказал один мой знакомый: "Зачем писать на Java, если есть Kotlin".

      В Kotlin очень много "синтаксического сахара", который позволяет очень сильно сократить код и сделать его более читабельным.


  1. stgunholy
    26.10.2021 12:08

    Для логгирования использую

    inline fun <reified T:Any> loggerFor(): Logger = LoggerFactory.getLogger(T::class.java)

    и потом в классах:

        val log = loggerFor<AccessController>()


    1. Borz
      26.10.2021 12:51

      но зачем так, когда есть https://github.com/MicroUtils/kotlin-logging ?..


      1. stgunholy
        26.10.2021 12:55

        Зачем ради одной строчки тянуть зависимость, которая мне больше не для чего не нужна?


        1. Borz
          26.10.2021 13:22

          она даёт больше 1 строчки. например Lazy-логгирование, когда значения вычисляются только если включен необходимый уровень логгирования или удобную обёртку к MDC


          1. stgunholy
            26.10.2021 13:39

            MDC это одна строчка со стандартным апи:

            MDC.put("username", username)

            Lazy - тоже стандартный апи:

            log.info("Searching responses with query: {}...", q) 


            1. Borz
              26.10.2021 14:15

              MDC это одна строчка со стандартным апи

              как минимум 2 строчки - добавить, а потом ещё и удалить по завершению контекста. При этом, не забыть вернуть предыдущее значение, если такой параметр MDC уже был ранее - это ещё N строк кода.
              гораздо удобнее "завернуться" в блок, который это сделает за вас:

              withLoggingContext("USER_ID" to userEntity.id) {
                // code
              }

              Но если вам хватает 1 строчки кода - это хорошо


            1. pyltsinm Автор
              26.10.2021 15:47
              +1

              Lazy - тоже стандартный апи:

              Не совсем. В этом случае всегда будет вызываться q.toString(). Если, например, есть логирование entity - это может вызвать запрос в базу.

              Когда используем библиотеку

              logger.debug { "hello $message" }

              message.toString() вызывается только если здесь соответствующий уровень логирования


              1. stgunholy
                26.10.2021 16:02
                +1

                Better yet, use parameterized messages

                There exists a very convenient alternative based on message formats. Assuming entry is an object, you can write:

                Object entry = new SomeObject();logger.debug("The entry is {}.", entry);

                After evaluating whether to log or not, and only if the decision is affirmative, will the logger implementation format the message and replace the '{}' pair with the string value of entry. In other words, this form does not incur the cost of parameter construction in case the log statement is disabled.


                1. pyltsinm Автор
                  26.10.2021 17:54

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


                1. Borz
                  27.10.2021 00:38

                  усложните вызов до:

                  logger.debug("The child entry is {}.", entry.child);

                  в какой момент будет получен child?


                  1. tbl
                    29.10.2021 12:21

                    Если поле, как у вас записано, то сразу (извлечение ссылки на поле - это вычисление смещения, почти бесплатно). Если же у вас метод с вложенной логикой, то можно Supplier использовать:

                    logger.debug("The child entry is {}.", entry::child);


    1. UScorp
      26.10.2021 14:08

      А я пока так делаю:

      companion object {

      private val log = LoggerFactory.getLogger(this::class.java)

      }


      1. stgunholy
        26.10.2021 14:09

        Да, я тоже с этого начинал, потом родил однострочечнек свой, и на этом успокоился :)


    1. deviantstudio
      11.11.2021 21:56

      так еще проще
      ```
      val logster: ReadOnlyProperty<Any, Logger> get() = LoggerDelegate()

      class LoggerDelegate : ReadOnlyProperty<Any, Logger> {

      lateinit var logger: Logger

      override fun getValue(thisRef: Any, property: KProperty<*>): Logger {

      if (!::logger.isInitialized) logger = LoggerFactory.getLogger(thisRef.javaClass)

      return logger

      }

      }
      ```
      и потом
      ```private val logger by logster```