Сравнение производительности 5 распространенных инструментов сопоставления компонентов

Java

Эта статья была переведена JavaGuide с сайта https://www.baeldung.com/java-performance-mapping-frameworks. При перепечатке просьба указывать адрес оригинала и автора перевода.

1. Введение

Создание больших приложений Java, состоящих из нескольких уровней, требует использования нескольких моделей предметной области, таких как модели постоянства, модели предметной области или так называемые DTO. Использование нескольких моделей для разных уровней приложений потребует от нас предоставления способа сопоставления между bean-компонентами. Выполнение этого вручную может быстро создать много шаблонного кода и занять много времени. К счастью, в Java есть несколько фреймворков отображения объектов. В этом уроке мы сравним производительность самых популярных картографических фреймворков Java.

Основываясь на ежедневном использовании и соответствующих тестовых данных, я лично считаю, что две платформы сопоставления компонентов MapStruct и ModelMapper являются лучшим выбором.

2. Обзор общих фреймворков сопоставления компонентов

2.1. Dozer

Dozer — это платформа сопоставления, которая использует рекурсию для копирования данных из одного объекта в другой. Фреймворк может не только копировать свойства между бинами, но и автоматически преобразовывать между разными типами.

Чтобы использовать структуру Dozer, нам нужно добавить эту зависимость в наш проект:

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.5.1</version>
</dependency>

Подробнее о Dozer можно узнать в официальной документации: http://dozer.sourceforge.net/documentation/gettingstarted.html, или вы также можете прочитать эту статью: https://www.baeldung.com/dozer.

2.2. Orika

Orika — это фреймворк сопоставления bean-to-bean, который рекурсивно копирует данные из одного объекта в другой.

Orika работает аналогично Dozer. Основное различие между ними заключается в том, что Orika использует генерацию байт-кода. Это позволяет создавать более быстрые мапперы с минимальными накладными расходами.

Чтобы использовать фреймворк Orika, нам нужно добавить эту зависимость в наш проект:

<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.2</version>
</dependency>

Подробнее об Orika можно узнать в официальной документации: https://orika-mapper.github.io/orika-docs/, или вы также можете прочитать эту статью: https://www.baeldung.com/orika-mapping.

2.3. MapStruct

MapStruct — это генератор кода, который автоматически создает классы сопоставления компонентов. MapStruct также может преобразовывать различные типы данных. Адрес Github: https://github.com/mapstruct/mapstruct.

Чтобы использовать структуру MapStruct, нам нужно добавить эту зависимость в наш проект:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>

Подробнее о MapStruct можно узнать в официальной документации: https://mapstruct.org/ или прочитать эту статью: https://www.baeldung.com/mapstruct.

Чтобы использовать структуру MapStruct, нам нужно добавить эту зависимость в наш проект:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>

2.4. ModelMapper

ModelMapper — это платформа, предназначенная для упрощения сопоставления объектов путем определения того, как объекты сопоставляются между объектами по соглашению. Он предоставляет типобезопасный и безопасный для рефакторинга API.

Подробнее о ModelMapper можно узнать в официальной документации: http://modelmapper.org/.

Чтобы использовать инфраструктуру ModelMapper, нам нужно добавить эту зависимость в наш проект:

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>1.1.0</version>
</dependency>

2.5. JMapper

JMapper — это инфраструктура сопоставления, предназначенная для обеспечения простого в использовании и высокопроизводительного сопоставления между компонентами Java. Фреймворк предназначен для применения принципа DRY с использованием аннотаций и реляционного отображения. Фреймворк допускает различные способы настройки: на основе аннотаций, на основе XML или API.

Подробнее о JMapper можно узнать в официальной документации: https://github.com/jmapper-framework/jmapper-core/wiki.

Чтобы использовать фреймворк JMapper, нам нужно добавить эту зависимость в наш проект:

<dependency>
    <groupId>com.googlecode.jmapper-framework</groupId>
    <artifactId>jmapper-core</artifactId>
    <version>1.6.0.1</version>
</dependency>

3. Протестируйте модель

Чтобы иметь возможность правильно протестировать сопоставление, нам нужны исходная и целевая модели. Мы создали две тестовые модели.

Первый — это простой POJO только с одним строковым полем, что позволяет нам сравнивать кадры в более простых случаях и проверять, не изменится ли что-нибудь, если мы используем более сложные bean-компоненты.

Простая исходная модель выглядит следующим образом:

public class SourceCode {
    String code;
    // getter and setter
}

Его цели также схожи:

public class DestinationCode {
    String code;
    // getter and setter
}

Практический пример исходного компонента выглядит следующим образом:

public class SourceOrder {
    private String orderFinishDate;
    private PaymentType paymentType;
    private Discount discount;
    private DeliveryData deliveryData;
    private User orderingUser;
    private List<Product> orderedProducts;
    private Shop offeringShop;
    private int orderId;
    private OrderStatus status;
    private LocalDate orderDate;
    // standard getters and setters
}

Целевой класс показан на следующем рисунке:

public class Order {
    private User orderingUser;
    private List<Product> orderedProducts;
    private OrderStatus orderStatus;
    private LocalDate orderDate;
    private LocalDate orderFinishDate;
    private PaymentType paymentType;
    private Discount discount;
    private int shopId;
    private DeliveryData deliveryData;
    private Shop offeringShop;
    // standard getters and setters
}

Всю структуру модели можно найти здесь: https://github.com/eugenp/tutorials/tree/master/performance-tests/src/main/java/com/baeldung/performancetests/model/source.

4. Преобразователь

Чтобы упростить дизайн тестовой установки, мы создали интерфейс преобразователя, как показано ниже:

public interface Converter {
    Order convert(SourceOrder sourceOrder);
    DestinationCode convert(SourceCode sourceCode);
}

Все наши пользовательские картографы будут реализовывать этот интерфейс.

4.1. OrikaConverter

Orika поддерживает полную реализацию API, что значительно упрощает создание маппера:

public class OrikaConverter implements Converter{
    private MapperFacade mapperFacade;

    public OrikaConverter() {
        MapperFactory mapperFactory = new DefaultMapperFactory
          .Builder().build();

        mapperFactory.classMap(Order.class, SourceOrder.class)
          .field("orderStatus", "status").byDefault().register();
        mapperFacade = mapperFactory.getMapperFacade();
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return mapperFacade.map(sourceOrder, Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return mapperFacade.map(sourceCode, DestinationCode.class);
    }
}

4.2. DozerConverter

Для Dozer требуется XML-файл сопоставления, который состоит из следующих частей:

<mappings xmlns="http://dozer.sourceforge.net"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://dozer.sourceforge.net
  http://dozer.sourceforge.net/schema/beanmapping.xsd">

    <mapping>
        <class-a>com.baeldung.performancetests.model.source.SourceOrder</class-a>
        <class-b>com.baeldung.performancetests.model.destination.Order</class-b>
        <field>
            <a>status</a>
            <b>orderStatus</b>
        </field>
    </mapping>
    <mapping>
        <class-a>com.baeldung.performancetests.model.source.SourceCode</class-a>
        <class-b>com.baeldung.performancetests.model.destination.DestinationCode</class-b>
    </mapping>
</mappings>

Как только карта XML определена, мы можем использовать ее из кода:

public class DozerConverter implements Converter {
    private final Mapper mapper;

    public DozerConverter() {
        DozerBeanMapper mapper = new DozerBeanMapper();
        mapper.addMapping(
          DozerConverter.class.getResourceAsStream("/dozer-mapping.xml"));
        this.mapper = mapper;
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return mapper.map(sourceOrder,Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return mapper.map(sourceCode, DestinationCode.class);
    }
}

4.3. MapStructConverter

Определение структуры Map очень простое, потому что оно полностью основано на генерации кода:

@Mapper
public interface MapStructConverter extends Converter {
    MapStructConverter MAPPER = Mappers.getMapper(MapStructConverter.class);

    @Mapping(source = "status", target = "orderStatus")
    @Override
    Order convert(SourceOrder sourceOrder);

    @Override
    DestinationCode convert(SourceCode sourceCode);
}

4.4. JMapperConverter

JMapperConverter нуждается в доработке. После реализации интерфейса:

public class JMapperConverter implements Converter {
    JMapper realLifeMapper;
    JMapper simpleMapper;

    public JMapperConverter() {
        JMapperAPI api = new JMapperAPI()
          .add(JMapperAPI.mappedClass(Order.class));
        realLifeMapper = new JMapper(Order.class, SourceOrder.class, api);
        JMapperAPI simpleApi = new JMapperAPI()
          .add(JMapperAPI.mappedClass(DestinationCode.class));
        simpleMapper = new JMapper(
          DestinationCode.class, SourceCode.class, simpleApi);
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return (Order) realLifeMapper.getDestination(sourceOrder);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return (DestinationCode) simpleMapper.getDestination(sourceCode);
    }
}

Нам также нужно добавить в каждое поле целевого класса@JMapПримечания. Кроме того, JMapper не может выполнять преобразование между типами перечислений, для этого требуется, чтобы мы создавали собственные функции сопоставления:

@JMapConversion(from = "paymentType", to = "paymentType")
public PaymentType conversion(com.baeldung.performancetests.model.source.PaymentType type) {
    PaymentType paymentType = null;
    switch(type) {
        case CARD:
            paymentType = PaymentType.CARD;
            break;

        case CASH:
            paymentType = PaymentType.CASH;
            break;

        case TRANSFER:
            paymentType = PaymentType.TRANSFER;
            break;
    }
    return paymentType;
}

4.5. ModelMapperConverter

ModelMapperConverter просто должен предоставить класс, который мы хотим отобразить:

public class ModelMapperConverter implements Converter {
    private ModelMapper modelMapper;

    public ModelMapperConverter() {
        modelMapper = new ModelMapper();
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
       return modelMapper.map(sourceOrder, Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return modelMapper.map(sourceCode, DestinationCode.class);
    }
}

5. Простое тестирование модели

Для тестирования производительности мы можем использовать Java Microbenchmark Harness, дополнительную информацию о том, как его использовать, можно найти в этой статье: https://www.baeldung.com/java-microbenchmark-harness.

Мы создали отдельный бенчмарк для каждого конвертера и указали режим бенчмарка Mode.All.

5.1 Среднее время

Для среднего времени выполнения JMH возвращает следующие результаты (чем меньше, тем лучше):

AverageTime

Этот тест ясно показывает, что и MapStruct, и JMapper имеют лучшее среднее время работы.

5.2. Пропускная способность

В этом режиме бенчмарк возвращает количество операций в секунду. Получаем следующие результаты (чем больше, тем лучше):

Throughput

В режиме пропускной способности MapStruct оказался самым быстрым среди тестовых фреймворков, за ним следует JMapper.

5.3. SingleShotTime

Этот режим позволяет измерять время от начала до конца одной операции. Бенчмарк дал следующие результаты (чем меньше, тем лучше):

SingleShotTime

Здесь мы видим, что JMapper дает намного лучшие результаты, чем MapStruct.

5.4. время выборки

Этот режим позволяет замерять время каждой операции. Результаты для трех разных процентилей следующие:

SampleTime

Все тесты показывают, что в зависимости от сценария и MapStruct, и JMapper являются хорошим выбором, хотя MapStruct дает гораздо худшие результаты для SingleShotTime.

6. Тестирование реальной модели

Для тестирования производительности мы можем использовать Java Microbenchmark Harness, дополнительную информацию о том, как его использовать, можно найти в этой статье: https://www.baeldung.com/java-microbenchmark-harness.

Мы создали отдельный бенчмарк для каждого конвертера и указали режим бенчмарка Mode.All.

6.1 Среднее время

JMH возвращает следующие средние результаты выполнения (чем меньше, тем лучше):

平均时间

Бенчмарк ясно показывает, что и MapStruct, и JMapper имеют лучшее среднее время работы.

6.2. Пропускная способность

В этом режиме бенчмарк возвращает количество операций в секунду. Получаем следующие результаты (чем больше, тем лучше):

В режиме пропускной способности MapStruct оказался самым быстрым среди тестовых фреймворков, за ним следует JMapper.

6.3. SingleShotTime

Этот режим позволяет измерять время от начала до конца одной операции. Бенчмарк дал следующие результаты (чем меньше, тем лучше):

6.4. время выборки

Этот режим позволяет замерять время каждой операции. Результаты для трех разных процентилей следующие:

SampleTime

Хотя точные результаты простого и реального примеров явно различаются, их тенденции совпадают. Оба примера дают схожие результаты с точки зрения того, какой алгоритм самый быстрый, а какой самый медленный.

6.5 Заключение

Основываясь на тестах реальной модели, которые мы провели в этом разделе, мы видим, что наилучшая производительность явно принадлежит MapStruct. В том же тесте мы увидели, что Dozer постоянно находится в нижней части таблицы результатов.

7. Суммировать

В этом посте мы выполнили тесты производительности на пяти популярных платформах отображения Java Bean: ModelMapper, MapStruct ,Орика, Дозер, JMapper.

Адрес примера кода: https://github.com/eugenp/tutorials/tree/master/performance-tests.

Рекомендация проекта с открытым исходным кодом

Другие рекомендации автора по проектам с открытым исходным кодом:

  1. JavaGuide: [Изучение Java + интервью] Обложка, которая охватывает основные знания, которые необходимо освоить большинству Java-программистов.
  2. springboot-guide: Учебное пособие по Spring Boot, подходящее для начинающих и опытных разработчиков (поддержка в свободное время, добро пожаловать в совместную поддержку).
  3. programmer-advancement: Я думаю, некоторые хорошие привычки, которые должны быть у техников!
  4. spring-security-jwt-guide:Начинать с нуля! Spring Security с JWT (включая проверку авторизации) бэкэнд-часть кода.

публика

我的公众号