JavaRush /Java блог /Random UA /Що таке Mapstruct і як правильно налаштувати його для мод...
Roman Beekeeper
35 рівень

Що таке Mapstruct і як правильно налаштувати його для модульного тестування у SpringBoot додатках

Стаття з групи Random UA

Передісторія

Всім привіт, мої дорогі друзі та читачі! Перед тим, як писатимемо статтю, трохи передісторії… Нещодавно зіткнувся з однією проблемою в роботі з бібліотекою Mapstruct , яку швидко описав у своєму телеграм-каналі тут . У коментарях проблему до запису вирішабо, у цьому допоміг мій колега за минулим проектом. Що таке Mapstruct і як правильно налаштувати його для модульного тестування у SpringBoot додатках.  Частина 1 - 1Після цього я вирішив написати на цю тему статтю, але ми звичайно ж не будемо вузько дивитися і постараємося спочатку увійти в курс справи, зрозуміти, що таке Mapstruct і навіщо він потрібен, і вже на реальному прикладі розберемо ситуацію, що виникла, і як її вирішити. Тому рекомендую зробити всі викладки паралельно читання статті, щоб відчути все на практиці. Перед початком – підпишись на мій телеграм-каналя там збираю свою діяльність, пишу думки про розробку на Java і IT в цілому. Підписався? Чудово! Ну, що ж, тепер поїхали!

Mapstruct, чаво?

A code generator for fast type-safe bean mappings. Перше наше завдання – розібратися, що таке Mapstruct і навіщо він нам. Загалом і загалом можна почитати про нього на офіційному сайті. На головній сторінці сайту написано три відповіді на запитання: що це таке? навіщо? як? Постараємось і ми так зробити:

Що це таке?

Mapstruct - це бібліотека, яка допомагає зіставляти (маппит, в цілому, так завжди і кажуть: маппит, замапит і т.д.) об'єкти однієї сутності в об'єкти іншої сутності за допомогою згенерованого коду на основі конфігурацій, що описуються через інтерфейси.

Навіщо?

Здебільшого ми розробляємо багатошарові програми (шар роботи з базою, шар бізнес логіки, шар взаємодії програми із зовнішнім світом) і кожен шар має свої об'єкти для зберігання та обробки даних. І ці дані потрібно передавати із шару в шар шляхом переведення з однієї сутності до іншої. Для тих, хто не працював із таким підходом, це може здатися дещо складним. Наприклад, ми маємо сутність до бази даних Student. Коли дані цієї сутності переходять у шар бізнес-логіки (сервісів), нам потрібно перевести дані із класу Student до класу StudentModel. Далі, після всіх маніпуляцій з бізнес-логікою, дані потрібно видати назовні. І для цього ми маємо клас StudentDto. Зрозуміло, нам потрібно передати дані з класу StudentModel до StudentDto. Писати руками щоразу методи, які переноситимуть, трудомістко. Плюс це зайвий код кодової бази, який потрібно підтримувати. Можна припуститися помилки. А Mapstruct такі методи генерує на етапі компіляції та зберігає у generated-sources.

Як?

За допомогою анотацій. Нам необхідно просто створити інструкцію, в якій буде головна інструкція Mapper, яка скаже бібліотеці, що методи в цьому інтерфейсі можна використовувати для перекладу з одних об'єктів в інші. Як я сказав раніше про студентів, у нашому випадку це буде інтерфейс StudentMapper, в якому будуть кілька методів перегонки даних з одного шару в інший:
public class Student {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentDTO {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentModel {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}
Для цих класів створюємо маппер (тут і далі так ми називатимемо інтерфейс, який описує що ми хочемо перевести і куди):
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
Краса цього підходу в тому, що якщо в різних класах імена та тип полів збігаються (як у нашому випадку), то налаштувань для Mapstruct вистачить, щоб на підставі інтерфейсу StudentMapper на етапі компіляції згенерувати потрібну реалізацію, яка перекладатиме. Так стало зрозуміліше, так? Підемо далі, і на реальному прикладі розберемо роботу в Spring Boot додатку.

Приклад роботи Spring Boot та Mapstruct

Перше, що нам потрібно, це створити Spring Boot проект і додати до нього Mapstruct. Для цієї справи я маю організацію в GitHub з шаблонами для репозиторіїв і старт для Spring Boot один з них. На його основі створюємо новий проект: Що таке Mapstruct і як правильно налаштувати його для модульного тестування у SpringBoot додатках.  Частина 1 - 2Далі отримаємо проект . Так, друзі, ставте зірку проекту , якщо знайшли його корисним, то я знатиму, що роблю це не дарма. У цьому проекті розкриємо ситуацію, яку я отримав на роботі та описав у пості у себе в телеграм-каналі. Коротко змалюю ситуацію для тих, хто не в темі: коли пишемо тести на мапери (тобто на ті реалізації інтерфейсів, про які ми говорабо раніше) хочеться, щоб тести проходабо якнайшвидше. Найпростіший варіант з маперами - це під час запуску тесту використовувати анотацію SpringBootTest, яка підніме весь ApplicationContext Spring Boot програми та ін'єктує потрібний для тесту мапер всередину тесту. Але цей варіант ресурсомісткий і займає значно більше часу, тому він не підходить. Нам потрібно писати модульний (unit) тест, який просто створив потрібний маппер і перевірив, що його методи працюють саме так, як ми очікуємо. Навіщо потрібно, щоб швидше йшли тести? Якщо тести проходять довго, це уповільнює весь процес розробки. Поки тести не пройдуть на новому коді, цей код не можна вважати вірним і його не візьмуть у тестування, а отже, його не візьмуть у продакшн і означає, що роботу розробник не виконав. Здавалося б, навіщо писати тест на бібліотеку, робота якої не підлягає сумніву? І все ж писати тест потрібно, тому що ми тестуємо те, наскільки правильно описали маппер і чи робить він те, що ми очікуємо. Насамперед, щоб полегшити нам роботу, додамо Lombok в наш проект, шляхом додавання ще однієї залежності в pom.xml:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
У нашому проекті нам потрібно буде перевести з model класів (які використовуються для роботи з бізнес-логікою) до класів DTO, які використовуємо для комунікації із зовнішнім світом. У нашому спрощеному варіанті ми будемо припускати, що поля не змінюються і наші мапери будуть простими. Але якщо буде бажання, можна буде написати більш розгорнуту статтю про те, як працювати з Mapstruct, як його налаштовувати, як користуватися його перевагами. Але потім, оскільки ця стаття вийде чималенькою. Припустимо, у нас є студент зі списком лекцій та лекторів, які він відвідує. Створимо пакет model . На основі цього створимо просту модель:
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
його лекції
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
та лектори
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
І створимо пакет dto поруч із пакетом model :
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
лекції
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
та лектори
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Тепер створимо маппер, який перекладатиме колекцію моделей лекцій до колекції DTO лекцій. Перше, що потрібно зробити – це додати Mapstruct у проект. Для цього скористаємося їх офіційним сайтом , там все описано. Тобто нам потрібно додати одну залежність і плагін до нашого пам'ятника (якщо є питання про те, що таке пам'ятник, — ось, будь ласка, Стаття1 та Стаття2 ):
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
і в пам'ятнику блоку <build/>. якого в нас ще не було:
<build>
   <plugins>
       <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>
                   <path>
                       <groupId>org.mapstruct</groupId>
                       <artifactId>mapstruct-processor</artifactId>
                       <version>1.4.2.Final</version>
                   </path>
               </annotationProcessorPaths>
           </configuration>
       </plugin>
   </plugins>
</build>
Далі створимо пакет mapper поряд з dto та model . На основі тих класів, що ми показали раніше, потрібно буде створити ще п'ять маперів:
  • Мапер LectureModel <-> LectureDTO
  • Мапер List<LectureModel> <-> List<LectureDTO>
  • Мапер LecturerModel <-> LecturerDTO
  • Мапер List<LecturerModel> <-> List<LecturerDTO>
  • Мапер StudentModel <-> StudentDTO
Поїхали:

LectureMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LecturerDTO dto);
}

LectureListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LectureMapper.class)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}

LecturerMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LectureDTO dto);
}

LecturerListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LecturerModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LecturerMapper.class)
public interface LecturerListMapper {
   List<LecturerModel> toModelList(List<LecturerDTO> dloList);
   List<LecturerDTO> toDTOList(List<LecturerModel> modelList);
}

StudentMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.StudentDTO;
import com.github.romankh3.templaterepository.springboot.model.StudentModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
public interface StudentMapper {
   StudentDTO toDTO(StudentModel model);
   StudentModel toModel(StudentDTO dto);
}
Потрібно окремо відзначити, що в Мапер ми посилаємося на інші Мапер. Робиться це за допомогою поля uses в анотації Mapper, як це зроблено в StudentMapper:
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
Тут ми використовуємо два маппери, щоб правильно замапити список лекцій та список лекторів. Тепер потрібно скомпілювати наш код та подивитися, що там і як. Зробити це можна за допомогою команди mvn clean compile . Але, як виявилося, при створенні реалізацій для Mapstruct наших маперів реалізації маперів не перезаписували поля. Чому? Виявилося, що не вдалося підхопити інструкцію Data від Lombok. І щось треба було робити… Тож у нас у статті з'явився новий розділ.

Зв'язуємо Lombok та Mapstruct

Після декількох хвабон пошуку, з'ясувалося, що потрібно певним чином з'єднати Lombok та Mapstruct. У документації Mapstruct є інформація про це . Після дослідження прикладу, який запропонували розробники з Mapstruct, оновимо наш pom.xml: Додамо окремо версії:
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
Додамо недостатню залежність:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
І оновимо наш компайлер-плагін, щоб він таки зміг поєднати Lombok та Mapstruct:
<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>
           <path>
               <groupId>org.mapstruct</groupId>
               <artifactId>mapstruct-processor</artifactId>
               <version>${org.mapstruct.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <version>${lombok.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok-mapstruct-binding</artifactId>
               <version>${lombok-mapstruct-binding.version}</version>
           </path>
       </annotationProcessorPaths>
   </configuration>
</plugin>
Після цього має все вийти. Ще раз скомпілюємо наш проект. Але ж де шукати класи, які згенерував Mapstruct? Вони лежать у generated-sources: ${projectDir}/target/generated-sources/annotations/ Що таке Mapstruct і як правильно налаштувати його для модульного тестування у SpringBoot додатках.  Частина 1 - 3 Тепер, коли ми підготувалися до того, щоб усвідомити моє розчарування з посту про Mapstruct, спробуємо створити тести на мапери.

Пишемо тести на наші мапери

Я створимо швидкий і простий тест, який протестував би один з маперів у разі, коли ми створюємо інтеграційний тест і не морочимося про час його проходження:

LectureMapperTest

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class LectureMapperTest {

   @Autowired
   private LectureMapper mapperUnderTest;

   @Test
   void shouldProperlyMapModelToDto() {
       //given
       LectureModel model = new LectureModel();
       model.setId(11L);
       model.setName("lecture name");

       //when
       LectureDTO dto = mapperUnderTest.toDTO(model);

       //then
       Assertions.assertNotNull(dto);
       Assertions.assertEquals(model.getId(), dto.getId());
       Assertions.assertEquals(model.getName(), dto.getName());
   }

   @Test
   void shouldProperlyMapDtoToModel() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(11L);
       dto.setName("lecture name");

       //when
       LectureModel model = mapperUnderTest.toModel(dto);

       //then
       Assertions.assertNotNull(model);
       Assertions.assertEquals(dto.getId(), model.getId());
       Assertions.assertEquals(dto.getName(), model.getName());
   }
}
Тут анотацією SpringBootTest ми запускаємо весь applicationContext і вже з нього за допомогою анотації Autowired отримуємо необхідний клас для тестування. З точки зору швидкості та легкості написання тесту – це дуже добре. Тест успішно проходить, все гаразд. Але ми підемо іншою дорогою і напишемо модульний тест на маппер, наприклад, на LectureListMapper.
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl();

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
Так як реалізації, які генерує Mapstruct лежать в одному класпасі, що і наш проект, ми спокійно можемо використовувати їх у наших тестах. Виглядає все чудово - ніяких анотацій, створюємо найпростішим способом клас, що нам потрібний і все. Але коли ми запустимо тест, то зрозуміємо, що він впаде і в консолі буде NullPointerException… Все тому, що реалізація мапера LectureListMapper має вигляд:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T21:46:12+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   @Autowired
   private LectureMapper lectureMapper;

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
Якщо ми подивимося, то NPE (скорочення від NullPointerException), ми отримуємо саме від змінної lectureMapper, яка виявляється не ініціалізована. Але в нас у реалізації немає і конструктора, за допомогою якого ми могли б ініціалізувати змінну. Тут якраз закопано причину, чому Mapstruct реалізував маппер саме так! У Spring можна декількома способами додавати бины в класи, можна ін'єктувати через поле разом із анотацією Autowired, як зроблено вище, і можна ін'єктувати через конструктор. У такій проблемній ситуації я опинився на роботі, коли потрібно оптимізувати час виконання тестів. Я думав, що зробити з цим нічого не можна і вабов свій біль на своєму телеграм-каналі. І тут мені у коментарях допомогли, сказали, що є можливість налаштувати стратегію ін'єктування. В інтерфейсі Mapper є поле injectionStrategy , яке якраз і приймає інам InjectionStrategyу якого два значення: FIELD та CONSTRUCTOR . Тепер, знаючи про це, додамо в наші мапери це налаштування, покажу на прикладі LectureListMapper :
@Mapper(componentModel = "spring", uses = LectureMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}
Виділив жирним частину, що додав. Додамо цю опцію для всіх інших і перекомпілюємо проект, щоб згенерувалися мапери вже з новим рядком. Коли це здійснимо, подивимося як змінилася реалізація мапера для LectureListMapper (виділив жирним ту частину, що нам потрібна):
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T22:25:37+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   private final LectureMapper lectureMapper;

   @Autowired
   public LectureListMapperImpl(LectureMapper lectureMapper) {

       this.lectureMapper = lectureMapper;
   }

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
І тепер Mapstruct реалізував ін'єктування маппера через конструктор. Власне, чого ми і домагалися. Тепер наш тест перестане компілюватися, оновимо його та отримаємо:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl(new LectureMapperImpl());

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
Тепер, якщо ми запустимо тест, то все спрацює так, як належить, тому що в LectureListMapperImpl ми передаємо необхідний йому LectureMapper… Перемога! Вам не складно, а мені приємно: Друзі, як завжди, підписуйтесь на мій гітхаб-аккаунт , на телеграм-аккаунт . Там я викладаю результат своєї діяльності, є реально корисні речі) Особливо запрошую вступити до групи обговорень телеграм-каналу . Так склалося, що якщо хтось має технічне питання, там можна отримати відповідь. Такий формат цікавий для всіх, можна почитати хто що вміє і набратися досвіду.

Висновок

У рамках цієї статті ми познайомабося з таким потрібним продуктом, що часто використовується як Mapstruct. Розібрали, що це, навіщо і як. На реальному прикладі помацали, що можна робити і як можна міняти. Також розібрали як налаштувати ін'єктування бінів через конструктор, щоб була можливість нормально проводити тестування маперів. Колеги з Mapstruct надали користувачам їхнього продукту вибирати яким саме способом ін'єктувати маппери, за що їм, безперечно, дякую. АЛЕ, незважаючи на те, що Spring рекомендує ін'єктувати бін через конструктор, хлопці з Mapstruct поставабо за умовчанням ін'єктування через поле. Чому так? Нема відповіді. Я підозрюю, що можуть бути причини, про які ми просто не знаємо, і тому вони так зробабо. І щоб дізнатися про них, я створив GitHub issueв їхньому офіційному репозиторії продукту.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ