Java
Kotlin
13 December 2019

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

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

image


Проблема


Долгое время программировал на java и spring boot 1.5. Но возникла потребность написать новый проект:

  1. Имею json для интеграции 1600 строк, некоторые классы имеют 100 полей
  2. Захотелось опробовать Kotlin и Quarkus.
  3. Написать rest controller, который бы умел работать с kotlin data class без аннотаций и без привлечения магии lombok. Хочется, чтобы data class был небольшого размера

Вы наверное догадались, что kotlin data class — это неизменяемый класс, или immutable. Класс у которого конструктор содержит все поля. Я большой приверженец такой концепции; после создания класса, его нельзя изменить, в нем нет сеттеров. Как в мире докер image не может изменен, так и дата класс, который попал в контроллер, это то, чего нельзя менять.

Давайте рассмотрим возможные пути решения проблемы, точнее как в современных проектах можно написать контроллер:

Стандартный вариант. вручную на spring или spring boot 1.5


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.
Немного многословный класс данных.


Можем ли мы сделать immutable?


Естественной реакцией на эту проблему будет решение удалить конструктор по умолчанию и запретить редактировать поля класса.
@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.

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

image

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

Второй вариант spring или spring boot 1.5 + аннотации как спасенье


Итак, мы осознали, что хотим immutable class, и знаем что пользуемся 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;
    }
}

Минусы: С аннотациями сильно раздувается класс. Очень многословно, на любителя.

Третий вариант spring или spring boot 1.5 + флаг lombok


Спасибо Throwable я процитирую из комментария:

«Если Вы используете Lombok, то есть лучший способ — прописать в lombok.config:

lombok.allArgsConstructor.addConstructorProperties = true

Это сгенерит на конструкторе @java.beans.ConstructorProperties, который Jackson умеет понимать.»

Замечательный вариант. Он наверняка бы спас меня от многословности аннотаций.

Минусы:

Но я хочу использовать Kotlin без lombok.
Я узнал этот вариант слишком поздно.


Четвертый вариант Spring boot 2.0


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

На удивление spring boot 2.0 спокойно работает с таким immutable классом. А также с его братом близнецом kotlin data class

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

Казалось бы 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 это и делает.

Пример проекта на spring boot 2.

Пятый вариант. Quarkus + Kotlin


У кваркуса есть пример rest service-а, аналог моего первого варианта. Т.е. rest контроллер по старинке. Однако, если вы хотите использовать его с Kotlin вам придется добавить флаги как это сделал spring boot 2.

Описание проблемы тут. Пример, как добавить в кваркус поддержку kotlin data class тут.

Выводы


Можно пользоваться первым простейшим вариантом создания rest контроллеров, но я бы посоветовал бы двигаться в сторону immutable классов. Пишите на Kotlin и вам не понадобится lombok. Код должен стать легче и проще. Я уверен, что создатели spring осознано шли на добавление javac -parameters в опции компилятора и в этом не должно быть криминала. Всем удачи на пути к идеальному коду.

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

+15
3k 48
Comments 9
Top of the day