Умеем ли мы готовить Java, Kotlin RestController?

в 13:09, , рубрики: java, kotlin, quarkus, rest, spring boot, Spring Boot 2

Практически любой программист на java в своей жизни писал RestController, но мало кто задумывается правильно ли он это делает. Даже если вы опытный программист, у вас могут возникнуть вопросы на которые я постараюсь ответить. В статье будут затронуты такие фреймворки как spring boot версии 1.5 и 2.0, а также quarkus — недавно появившийся соперник spring boot от red hat.

Quickstart

Rest контроллеры появились достаточно давно и типичным примером их использования на java, который есть во всех туториалах является следующий пример.

Просто создаем простейший контроллер

@RestController
public class FruitController {
   @PostMapping("/fruit")
   public void greeting(@RequestBody Fruit request) {
      System.out.println(request);
   }
}

И создаем POJO(plain old java object)

public class Fruit {
    public String name;
    public String description;
    public Fruit() {
    }
    public Fruit(String name, String description) {
        this.name = name;
        this.description = description;
    }
}

тут могут быть вариации с private полями и getters/setters или lombok аннотациями, но не суть.
Замечательно, мы создали первый рест контроллер он работает. Для 90% случаев рабочий вариант. Можно здесь и остановиться.

Неизменяемые классы или immutable

Однако постепенно приходит осознание, что данный POJO объект не должен меняться после того, как пришел в контроллер. Концепция immutable давно уже проникает в сознание программистов. Если у вас еще не появилось это ощущение, то скажу что зря, у создателей kotlin оно уже давно есть и они уже придумали data class, о них поговорим позже. Естественной реакцией на эту проблему это удаление конструктора по умолчанию и запрет редактировать поля класса.

@Getter
public class Fruit {
    private String name;
    private String description;
//    public Fruit() {
//    }
    public Fruit(String name, String description) {
        this.name = name;
        this.description = description;
    }
}

Но теперь возникает проблема, оказывается что библиотека (скорее всего jackson) не может создать класс. Наиболее вероятно вы увидим ошибку вроде No default constructor found. Если вы используете spring boot2 вы этого врядли увидите, попробуйте на 1.5, позже расскажем почему так происходит.

Значит jackson сначала создавал класс с помощью конструктора без параметров, а потом вызывал getter/setter. Какой ужас. Ведь есть же конструктор со всеми параметрами? Но к сожалению когда класс откомпилирован, параметры выглядят примерно так.

image

Т.е. во время выполнения java ничего не знает о именах параметров в конструкторе. Компилятор их теряет.

Аннотации как спасенье

На помощь приходят аннотации от jackson

@Getter
public class Fruit {
    private String name;
    private String description;

    @JsonCreator
    public Fruit(@JsonProperty("name") String name, @JsonProperty("description")String description) {
        this.name = name;
        this.description = description;
    }
}

В данный момент в нашем проекте на sping boot 1.5 этими аннотациями буквально пестрит все.
А если вы возьмете популярный генератор jsonschema2pojo, то он сгенерирует еще больше аннотаций. Чесно говоря мне они не нравится.

Попробуйте скопировать туда

{
	"description": "description",
	"name": "name"  
}

На выходе получаем(можно зажмуриться и пролистать):

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
        "description",
        "name"
})
public class Example {

    @JsonProperty("description")
    private String description;
    @JsonProperty("name")
    private String name;

    @JsonProperty("description")
    public String getDescription() {
        return description;
    }

    @JsonProperty("description")
    public void setDescription(String description) {
        this.description = description;
    }

    @JsonProperty("name")
    public String getName() {
        return name;
    }

    @JsonProperty("name")
    public void setName(String name) {
        this.name = name;
    }
}

Очень многословно, на любителя.

Kotlin data class и опция компилятора

Посмотрим что генерирует IntelliJ IDEA для данного json c помощью плагина Json to Kotlin Class.

data class Fruit(
 val name : String,
 val description : String)

В идеале я хочу пользоваться только такими простыми классами в моих рест контроллерах. По сути это аналог Pojo java класса без конструктора и сеттеров.

Казалось бы java в рантайме не знает имен параметров в конструкторе, но почему то spring boot2 уже умеет работать с data class. Итак заглянем в spring-boot-starter-parent там добавилась поддержка kotlin.

<plugin>
             <groupId>org.jetbrains.kotlin</groupId>
...
              <configuration>
                        <javaParameters>true</javaParameters>  
              </configuration>
</plugin>
<plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <configuration>
                        <parameters>true</parameters>
              </configuration>
</plugin>

Расшифровываю. Для того чтобы имена параметров в конструкторе класса не терялись при рантайме компилятору необходимо передать флаг javac -parameters И spring boot 2.0 это и делает.

Quarkus

А теперь посмотрим на quarkus. Он еще пока слишком молод и амбициозен. И данный флаг в него еще не успели добавить. Поэтому если вы решите использовать quarkus с kotlin, то у вас могут быть проблемы.

Примеры

Также я подготовил два небольших примера со spring boot 2.0 и с quarkus

Всем спасибо за внимание!

Автор: serg-bs

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js