JavaRush /جاوا بلاگ /Random-UR /Что такое Mapstruct и как правильно настроить его для мод...

Что такое Mapstruct и как правильно настроить его для модульного тестирования в SpringBoot приложениях

گروپ میں شائع ہوا۔

Предыстория

Всем привет, мои дорогие друзья и читатели! Перед тем, How будем писать статью, немного предыстории… Недавно столкнулся с одной проблемой в работе с библиотекой Mapstruct, которую бегло описал в своем телеграм-канале здесь. В комментариях проблему к записи решor, в этом помог мой коллега по прошлому проекту. What такое Mapstruct и How правильно настроить его для модульного тестирования в SpringBoot applicationsх. Часть 1 - 1После этого я решил написать на эту тему статью, но мы конечно же не будем узко смотреть и постараемся вначале войти в курс дела, понять, что такое Mapstruct и зачем он нужен, и уже на реальном примере разберем возникшую ранее ситуацию и How ее решить. Поэтому настоятельно рекомендую проделать все выкладки параллельно чтению статьи, дабы ощутить все на практике. Перед началом — подпишись на мой телеграм-канал, я там собираю свою деятельность, пишу мысли о разработке на Java и IT в целом. Подписался? Отлично! Ну что ж, теперь поехали!

Mapstruct, чаво?

A code generator for fast type-safe bean mappings. Первая наша задача ​​— разобраться, что такое Mapstruct и зачем он нам. В общем и целом можно почитать о нем на официальном сайте. На главной странице сайта написаны три ответа на вопросы: что это такое? зачем? How? Постараемся и мы так сделать:

What это такое?

Mapstruct — это библиотека, которая помогает сопоставлять (маппить, в целом, так всегда и говорят: маппить, замапить и т.д.) an objectы одних сущностей в an objectы других сущностей при помощи сгенерированного codeа на основе конфигураций, которые описываются через интерфейсы.

Зачем?

В большинстве своем мы разрабатываем многослойные applications (слой работы с базой, слой бизнес логики, слой взаимодействия applications с внешним миром) и каждый слой имеет свои an objectы для хранения и обработки данных. И эти данные нужно передавать из слоя в слой путем перевода из одной сущности в другую. Для тех, кто не работал с таким подходом, это может показаться несколько сложным. Например, у нас есть сущность к базе данных Student. Когда данные этой сущности переходят в слой бизнес-логики (сервисов), нам нужно перевести данные из класса Student в класс StudentModel. Далее, после всех манипуляций с бизнес логикой, данные нужно выдать наружу. И для этого у нас есть класс StudentDto. Разумеется, нам нужно передать данные из класса StudentModel в StudentDto. Писать руками каждый раз методы, которые будут переносить, трудоемко. Плюс это лишний code в codeовой базе, который нужно поддерживать. Можно допустить ошибку. А Mapstruct такие методы генерирует на этапе компиляции и хранит в generated-sources.

Как?

При помощи аннотаций. Нам необходимо просто создать аннотацию, в которой будет главная annotation Mapper, которая скажет библиотеке, что методы в этом интерфейсе можно использовать для перевода из одних an objectов в другие. Как я сказал раньше про студентов, в нашем случае это будет интерфейс 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);
}
Красота этого подхода в том, что если в разных классах имена и тип полей совпадают (How в нашем случае), то настроек для Mapstruct хватит, чтобы на основании интерфейса StudentMapper на этапе компиляции сгенерировать нужную реализацию, которая будет переводить. Так уже стало понятнее, да? Пойдем дальше, и на реальном примере разберем работу в Spring Boot приложении.

Пример работы Spring Boot и Mapstruct

Первое, что нам нужно — это создать Spring Boot проект и добавить в него Mapstruct. Для этого дела у меня есть организация в GitHub с шаблонами для репозиториев и старт для Spring Boot один из них. На его основе создаем новый проект: What такое Mapstruct и How правильно настроить его для модульного тестирования в SpringBoot applicationsх. Часть 1 - 2Далее получим проект. Да, друзья, ставьте звезду проекту, если нашли его полезным, так я буду знать, что делаю это не зря. В этом проекте раскроем ситуацию, которую я получил на работе и описал в посте у себя в телеграм-канале. Вкратце обрисую ситуацию для тех, кто не в теме: когда пишем тесты на мапперы (то есть на те реализации интерфейсов, о которых мы говорor ранее) хочется, чтобы тесты проходor How можно быстрее. Самый простой вариант с мапперами — это во время запуска теста использовать аннотацию SpringBootTest, которая поднимет весь ApplicationContext Spring Boot applications и инъектирует нужный для теста маппер внутрь теста. Но этот вариант ресурсоемкий и занимает значительно больше времени, поэтому для нас он не подходит. Нам нужно писать модульный (unit) тест, который бы просто создал нужный маппер и проверил, что его методы работают именно так, How мы ожидаем. Для чего нужно, чтобы быстрее шли тесты? Если тесты проходят долго, то это замедляет весь процесс разработки. Пока тесты не пройдут на новом codeе, этот code нельзя считать верным и его не возьмут в тестирование, а значит его не возьмут в продакшн и значит, что работу разработчик не выполнил. Казалось бы зачем писать тест на библиотеку, работа которой не подлежит сомнению? И все же писать тест нужно, потому что мы тестируем то, насколько правильно описали маппер и делает ли он то, что мы ожидаем. Первым делом, чтобы облегчить нам работу, добавим Lombok в наш проект, путем добавления еще одной зависимости в pom.xml:

<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
В нашем проекте, нам нужно будет перевести из model классов (которые используются для работы с бизнес-логикой) в классы DTO, которые используем для коммуникации с внешним миром. В нашем упрощенном варианте, мы будем предполагать, что поля не изменяются и наши мапперы будут простыми. Но, если будет желание, можно будет написать более развернутую статью о том, How работать с Mapstruct, How его настраивать, How пользоваться его преимуществами. Но потом, так How эта статья выйдет немаленькой. Допустим у нас есть студент со списком лекций и лекторов, которые он посещает. Создадим пакет 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, How это сделано в StudentMapper:

@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
Здесь мы используем два маппера, чтобы правильно замаппить список лекций и список лекторов. Теперь нужно скомпorровать наш code и посмотреть, что там и How. Сделать это можно при помощи команды mvn clean compile. Но, How оказалось, при создании реализаций для Mapstruct наших мапперов реализации мапперов не перезаписывали поля. Почему? Оказалось, что не получилось подхватить аннотацию Data от Lombok. И что-то нужно было делать… Поэтому у нас в статье появился новый раздел.

Связываем Lombok и Mapstruct

После нескольких minutes поиска, выяснилось, что нужно определенным образом соединить Lombok и Mapstruct. В documentации Mapstruct есть информация об этом. После исследования примера, который предложor разработчики из 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>
После этого должно все получиться. Еще раз скомпorруем наш проект. Но где же искать классы, которые сгенерировал Mapstruct? Они лежат в generated-sources: ${projectDir}/target/generated-sources/annotations/ What такое Mapstruct и How правильно настроить его для модульного тестирования в SpringBoot applicationsх. Часть 1 - 3Теперь, когда мы подготовorсь к тому, чтобы осознать мое разочарование из поста про 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());
   }
}
Так How реализации, которые генерирует Mapstruct лежат в одном класспасе, что и наш проект, то мы спокойно можем использовать их в наших тестах. Выглядит все прекрасно — ниHowих аннотаций, создаем самым простым способом класс, что нам нужен и все. Но когда мы запустим тест, то поймем, что он упадет и в консоли будет 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), мы получаем How раз от переменной lectureMapper, которая оказывается не инициализирована. Но у нас в реализации нет и конструктора, при помощи которого мы бы могли инициализировать переменную. Здесь How раз закопана причина, почему Mapstruct реализовал маппер именно так! В Spring можно несколькими способами добавлять бины в классы, можно инъектировать их через поле вместе с аннотацией Autowired, How сделано выше, а можно инъектировать через конструктор. В такой проблемной ситуации я оказался на работе, когда нужно было оптимизировать время выполнения тестов. Я думал, что сделать с этим ничего нельзя и излил свою боль на своем телеграм-канале. И тут мне в комментариях помогли, сказали, что есть возможность настроить стратегию инъектирования. В интерфейсе Mapper есть поле injectionStrategy, которое How раз и принимает енам 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);
}
Выделил жирным часть, что добавил. Добавим эту опцию для всех остальных и перекомпorруем проект, чтобы сгенерировались мапперы уже с новой строкой. Когда это выполним, посмотрим How изменилась реализация маппера для 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 реализовал инъектирование маппера через конструктор. Собственно чего мы и добивались. Теперь наш тест перестанет компorроваться, обновим его и получим:

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());
   }
}
Теперь, если мы запустим тест, то все сработает так How положено, так How в LectureListMapperImpl мы передаем необходимый ему LectureMapper… Победа! Вам не сложно, а мне приятно: Друзья, все How обычно, подписывайтесь на мой гитхаб-аккаунт, на телеграм-аккаунт. Там я выкладываю результат своей деятельности, есть реально полезные вещи) Особенно приглашаю вступить в группу обсуждений телеграм-канала. Так сложилось, что если у кого-то есть технический вопрос, там можно получить ответ. Такой формат интересен для всех, можно почитать, кто что умеет и набраться опыта.

Вывод

В рамках этой статьи мы познакомorсь с таким нужным и часто используемым продуктом How Mapstruct. Разобрали что это, зачем и How. На реальном примере пощупали, что можно делать и How можно менять. Также, разобрали How настроить инъектирование бинов через конструктор, чтобы была возможность нормально проводить тестирование мапперов. Коллеги из Mapstruct предоставor пользователям их продукта выбирать Howим именно способом инъектировать мапперы, за что им несомненно спасибо. НО, несмотря на то, что Spring рекомендует инъектировать бины через конструктор, ребята из Mapstruct поставor по умолчанию инъектирование через поле. Почему так? Нет ответа. Я подозреваю, что могут быть причины о которых мы просто не знаем, и поэтому они так сделали. И чтобы узнать у них, я создал GitHub issue в их официальном репозитории продукта.
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION