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

Некоторое время назад я написал рассказ о генерации кода с помощью проекта Lombok. Lombok позволяет нам писать меньше кода в наших POJO. Мы поддерживаем неизменность наших POJO, поэтому обычно создаем для них построители и геттеры (@Builder и @Value).

Некоторое время мы изо всех сил пытались найти библиотеку сопоставления, которая поддерживала сопоставление неизменяемых объектов и хорошо играла с нашими POJO, созданными Lombok. К счастью для нас, MapStruct стал тем инструментом, которого мы ждали, когда библиотека добавила поддержку сборщиков в v1.3.0.Beta1.

Почему картографическая библиотека

Мы можем сами написать логику отображения. Это не особенно сложно ... но может стать кошмаром в обслуживании. Незначительное изменение в POJO может привести к поломке картографов.

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

Использование библиотеки сопоставления предоставляет нам поведение по умолчанию для картографов, и, в результате, некоторые изменения в наших POJO не нарушат работу картографов. Кроме того, библиотека сопоставления позволяет нам писать меньше кода ... а код, который мы не пишем, всегда легче всего поддерживать.

Почему MapStruct

Пару абзацев назад мы выделили одну из причин: MapStruct прекрасно работает с Lombok и может отображать неизменяемые объекты с помощью построителей. Но это не единственная причина использования MapStruct.

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

Кроме того, MapStruct легко интегрируется со Spring, которую мы предпочитаем для большинства приложений Java.

Добавление MapStruct в наш проект

Нам нужно будет добавить зависимость к нашему pom.xml:

и добавьте конфигурацию для процессоров аннотаций MapStruct и Lombok:

Без лишних слов мы можем приступить к отображению объектов.

Базовый пример

Допустим, у нас есть эти два класса:

Наша бизнес-логика утверждает, что при сопоставлении Product с ApiProduct:

  • Product.externalId должен быть сопоставлен с ApiProduct.id.
  • Product.name должен быть сопоставлен с ApiProduct.caption.
  • Product.description должен быть сопоставлен с ApiProduct.description.

Картографы MapStruct - это интерфейсы (помеченные @Mapper), в которых мы определяем нужные нам методы сопоставления и включаем аннотации для управления процессом сопоставления. Процессор аннотаций Mapstruct сгенерирует реализацию для нашего интерфейса, следуя инструкциям, данным в наших аннотациях.

Давайте определим картограф MapStruct для нашего базового примера:

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

  • Когда свойство имеет одно и то же имя как в исходном, так и в целевом объекте, оно отображается неявно.
  • Если свойство существует в источнике, но не в целевом объекте, оно игнорируется.

Обработчик аннотаций, предоставляемый Mapstruct, сгенерирует во время компиляции реализацию для нашего интерфейса ProductMapper. Сгенерированный код выглядит так:

Как видите, когда мы включаем свойство componentModel = “spring” в аннотацию @Mapper, сгенерированный интерфейс является Spring @Component. Эта встроенная поддержка интеграции Spring упрощает использование картографов MapStruct в приложениях Spring.

Более чем один источник

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

Допустим, ApiProduct теперь включает поле categoryId:

В нашей модели есть Category POJO:

С помощью этих строительных блоков мы можем добавить новый параметр в наш картограф:

Как видите, теперь нам нужно полностью определить свойства источника, включая имя параметра, но в остальном процесс сопоставления остается в основном таким же. Код, сгенерированный Mapstruct для этого примера, будет:

Сложные сопоставления

В некоторых случаях нам нужен больший контроль над процессом сопоставления. Для этих сценариев MapStruct предоставляет нам возможность писать собственные выражения Java. Допустим, наша логика изменилась, и ApiProduct.id теперь должна быть строкой, полученной в результате объединения Product.id и Product.externalId. Мы можем изменить тип ApiProduct.id на String, а затем определить это отображение следующим образом:

Эти выражения внутри аннотации @Mapping не совсем легко читать, но мы можем немного реорганизовать код и воспользоваться методами по умолчанию, чтобы сделать его более читаемым:

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

Если ваши функции сопоставления являются чистыми функциями, вы можете просто переместить их в интерфейс и дать команду MapStruct импортировать этот интерфейс для вас, используя атрибут imports аннотации @Mapper. Обычно это наш предпочтительный подход. В нашем примере мы можем создать следующий интерфейс:

И перепишем наш маппер следующим образом:

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

Подведение итогов

MapStruct - это библиотека для генерации кода, которая генерирует для вас средства отображения объектов. Его легко настроить, он хорошо работает и отлично работает с неизменяемыми объектами, созданными с помощью проекта Lombok. Кроме того, MapStruct легко интегрируется со Spring, и, если вы являетесь пользователем IntelliJ IDEA, у вас даже есть плагин, который облегчит вам жизнь.

Вы не можете требовать большего. У вас нет оправдания, чтобы продолжать писать (и поддерживать) свои собственные средства отображения объектов.