В любом нетривиальном 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, у вас даже есть плагин, который облегчит вам жизнь.
Вы не можете требовать большего. У вас нет оправдания, чтобы продолжать писать (и поддерживать) свои собственные средства отображения объектов.