«1. Обзор

В этой статье мы узнаем, как использовать пользовательский преобразователь с библиотекой MapStruct.

Библиотека MapStruct используется для отображения между типами компонентов Java. Используя пользовательский сопоставитель с MapStruct, мы можем настроить методы сопоставления по умолчанию.

2. Зависимости Maven

Давайте добавим библиотеку mapstruct в наш Maven pom.xml:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.3.1.Final</version> 
</dependency>

Чтобы увидеть автоматически сгенерированные методы внутри целевой папки проекта, мы должны добавить annotationProcessorPaths к подключаемому модулю maven-compiler-plugin:

<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</artifactId>
                <version>1.3.1.Final</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

3. Пользовательский преобразователь

Пользовательские преобразователи используются для выполнения конкретных требований преобразования. Чтобы достичь этого, мы должны определить метод для преобразования. Затем мы должны уведомить MapStruct о методе. Наконец, MapStruct вызовет метод для преобразования исходного объекта в целевой.

Например, давайте представим, что у нас есть приложение, которое вычисляет отчет об индексе массы тела (ИМТ) пользователя. Чтобы рассчитать ИМТ, мы должны собрать значения тела пользователя. Чтобы преобразовать имперские единицы в метрические, мы можем использовать пользовательские методы сопоставления.

Есть два способа использования пользовательского картографа с MapStruct. Мы можем либо вызвать пользовательский метод, введя его в свойствеqualifiedByName аннотации @Mapping, либо создать для него аннотацию.

Прежде чем мы начнем, мы должны определить класс DTO для хранения имперских значений:

public class UserBodyImperialValuesDTO {
    private int inch;
    private int pound;
    // constructor, getters, and setters
}

Далее давайте определим класс DTO для хранения значений метрик:

public class UserBodyValues {
    private double kilogram;
    private double centimeter;
    // constructor, getters, and setters
}

3.1. Пользовательский преобразователь с методом

Чтобы начать использовать собственные преобразователи, давайте создадим интерфейс с аннотацией @Mapper:

@Mapper 
public interface UserBodyValuesMapper {
    //...
}

Во-вторых, давайте создадим наш собственный метод с типом возвращаемого значения, который мы хотим, и аргументом, который нам нужно преобразовать. Мы должны использовать аннотацию @Named с параметром value, чтобы сообщить MapStruct о пользовательском методе сопоставления:

@Mapper
public interface UserBodyValuesMapper {

    @Named("inchToCentimeter")
    public static double inchToCentimeter(int inch) {
        return inch * 2.54;
    }
 
    //...
}

И, наконец, давайте определим метод интерфейса сопоставления с помощью аннотации @Mapping. В этой аннотации мы сообщим MapStruct о типе источника, типе цели и методе, который он будет использовать:

@Mapper
public interface UserBodyValuesMapper {
    UserBodyValuesMapper INSTANCE = Mappers.getMapper(UserBodyValuesMapper.class);
    
    @Mapping(source = "inch", target = "centimeter", qualifiedByName = "inchToCentimeter")
    public UserBodyValues userBodyValuesMapper(UserBodyImperialValuesDTO dto);
    
    @Named("inchToCentimeter") 
    public static double inchToCentimeter(int inch) { 
        return inch * 2.54; 
    }
}

Давайте протестируем наш пользовательский преобразователь:

UserBodyImperialValuesDTO dto = new UserBodyImperialValuesDTO();
dto.setInch(10);

UserBodyValues obj = UserBodyValuesMapper.INSTANCE.userBodyValuesMapper(dto);

assertNotNull(obj);
assertEquals(25.4, obj.getCentimeter(), 0);

3.2. Пользовательский преобразователь с аннотацией

Чтобы использовать собственный преобразователь с аннотацией, мы должны определить аннотацию вместо аннотации @Named. Затем мы должны проинформировать MapStruct о вновь созданной аннотации, указав параметр квалифицированногоByName аннотации @Mapping.

Давайте посмотрим, как мы определяем аннотацию:

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PoundToKilogramMapper {
}

Давайте добавим аннотацию @PoundToKilogramMapper к нашему методу poundToKilogram:

@PoundToKilogramMapper
public static double poundToKilogram(int pound) {
    return pound * 0.4535;
}

Теперь давайте определим метод интерфейса сопоставления с аннотацией @Mapping. В аннотации сопоставления мы сообщаем MapStruct о типе источника, целевом типе и классе аннотации, который он будет использовать:

@Mapper
public interface UserBodyValuesMapper {
    UserBodyValuesMapper INSTANCE = Mappers.getMapper(UserBodyValuesMapper.class);

    @Mapping(source = "pound", target = "kilogram", qualifiedBy = PoundToKilogramMapper.class)
    public UserBodyValues userBodyValuesMapper(UserBodyImperialValuesDTO dto);

    @PoundToKilogramMapper
    public static double poundToKilogram(int pound) {
        return pound * 0.4535;
    }
}

Наконец, давайте протестируем наш пользовательский преобразователь:

UserBodyImperialValuesDTO dto = new UserBodyImperialValuesDTO();
dto.setPound(100);

UserBodyValues obj = UserBodyValuesMapper.INSTANCE.userBodyValuesMapper(dto);

assertNotNull(obj);
assertEquals(45.35, obj.getKilogram(), 0);

4. Заключение

В этой статье мы узнали, как использовать пользовательский преобразователь с библиотекой MapStruct.

Реализации этих примеров и тестов доступны на GitHub.