При работе над проектом часто необходимо конвертировать между PO, VO и DTO. Для простого преобразования объектов в принципе достаточно использования BeanUtils, но для сложного преобразования, если вы его используете, вам придется написать кучу методов Getter и Setter. Сегодня я рекомендую вам инструмент автоматического сопоставления объектов
MapStruct
, функция действительно мощная!
Адрес фактического проекта электронной коммерции SpringBoot (50k+star):GitHub.com/macro-positive/…
О BeanUtils
Обычно я часто использую класс BeanUtil в Hutool для реализации конвертации объектов, но попользовавшись им, я обнаружил некоторые недостатки:
- Отображение атрибутов объекта реализовано с использованием отражения, и производительность относительно низкая;
- Для свойств с разными именами или разными типами, которые не могут быть преобразованы, методы Getter и Setter должны быть написаны отдельно;
- Для вложенных подобъектов, которые также необходимо преобразовать, вам придется иметь с ними дело самостоятельно;
- При преобразовании объекта коллекции вы должны использовать цикл для копирования по одному.
Эти недостатки может решить MapStruct, и он достоин того, чтобы быть мощным инструментом картирования объектов!
Введение в MapStruct
MapStruct — это инструмент сопоставления атрибутов объектов, основанный на аннотациях Java, и он уже имеет 4,5 тысячи звезд на Github. При его использовании, если мы определяем правила сопоставления атрибутов объекта в интерфейсе, он может автоматически генерировать класс реализации сопоставления без использования отражения с отличной производительностью и может реализовывать различные сложные сопоставления.
Поддержка плагинов IDEA
Как очень популярный инструмент картографирования объектов, MapStruct также предоставляет специальный подключаемый модуль IDEA, который можно установить перед использованием.
Интеграция проекта
Интегрировать MapStruct в SpingBoot очень просто, достаточно добавить следующие две зависимости, которые используются здесь
1.4.2.Final
Версия.
<dependency>
<!--MapStruct相关依赖-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
основное использование
После интеграции MapStruct давайте испытаем его возможности и посмотрим, насколько он прекрасен!
Базовая карта
Давайте начнем с быстрого старта, познакомимся с основными функциями MapStruct и поговорим о принципе его реализации.
- Сначала мы подготавливаем объект PO для использования.
Member
;
/**
* 购物会员
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Member {
private Long id;
private String username;
private String password;
private String nickname;
private Date birthday;
private String phone;
private String icon;
private Integer gender;
}
- Затем подготовьте объект DTO члена
MemberDto
, нам надоMember
объект, преобразованный вMemberDto
объект;
/**
* 购物会员Dto
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberDto {
private Long id;
private String username;
private String password;
private String nickname;
//与PO类型不同的属性
private String birthday;
//与PO名称不同的属性
private String phoneNumber;
private String icon;
private Integer gender;
}
- Затем создайте интерфейс сопоставления
MemberMapper
, реализовать отображение атрибутов с одинаковым именем и типом, атрибутов с разными именами и атрибутов разных типов;
/**
* 会员对象映射
* Created by macro on 2021/10/21.
*/
@Mapper
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);
@Mapping(source = "phone",target = "phoneNumber")
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
MemberDto toDto(Member member);
}
- Далее создаем тестовый интерфейс в Контроллере, напрямую через интерфейс в
INSTANCE
метод преобразования вызова экземпляраtoDto
;
/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "基本映射")
@GetMapping("/baseMapping")
public CommonResult baseTest() {
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
MemberDto memberDto = MemberMapper.INSTANCE.toDto(memberList.get(0));
return CommonResult.success(memberDto);
}
}
- После запуска проекта протестируйте интерфейс в Swagger и обнаружите, что все атрибуты PO были успешно преобразованы в DTO.Адрес доступа Swagger:http://localhost:8088/swagger-ui
- На самом деле принцип реализации MapStruct очень прост, судя по тому, что мы используем в интерфейсе Mapper.
@Mapper
и@Mapping
и другие аннотации, класс реализации интерфейса генерируется во время выполнения, и мы можем открыть проектtarget
посмотреть справочник;
- Ниже представлена MapStruct для
MemberMapper
Сгенерированный код сопоставления объектов может попрощаться с написанными от руки геттерами и сеттерами!
public class MemberMapperImpl implements MemberMapper {
public MemberMapperImpl() {
}
public MemberDto toDto(Member member) {
if (member == null) {
return null;
} else {
MemberDto memberDto = new MemberDto();
memberDto.setPhoneNumber(member.getPhone());
if (member.getBirthday() != null) {
memberDto.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(member.getBirthday()));
}
memberDto.setId(member.getId());
memberDto.setUsername(member.getUsername());
memberDto.setPassword(member.getPassword());
memberDto.setNickname(member.getNickname());
memberDto.setIcon(member.getIcon());
memberDto.setGender(member.getGender());
return memberDto;
}
}
}
карта коллекции
MapStruct также предоставляет функцию сопоставления коллекций, которая может напрямую преобразовывать список PO в список DTO, и больше не нужно преобразовывать объекты один за другим!
- существует
MemberMapper
добавлено в интерфейсtoDtoList
метод преобразования списка;
/**
* 会员对象映射
* Created by macro on 2021/10/21.
*/
@Mapper
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);
@Mapping(source = "phone",target = "phoneNumber")
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
List<MemberDto> toDtoList(List<Member> list);
}
- Создайте тестовый интерфейс в Контроллере, напрямую через интерфейс Mapper
INSTANCE
метод преобразования вызова экземпляраtoDtoList
;
/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "集合映射")
@GetMapping("/collectionMapping")
public CommonResult collectionMapping() {
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
List<MemberDto> memberDtoList = MemberMapper.INSTANCE.toDtoList(memberList);
return CommonResult.success(memberDtoList);
}
}
- При тестировании интерфейса вызовов в Swagger список PO был преобразован в список DTO.
сопоставление дочерних объектов
MapStruct также поддерживает случай, когда объект содержит подобъекты, которые также необходимо преобразовать.
- Например, у нас есть объект заказа на поставку.
Order
, вложенный сMember
иProduct
объект;
/**
* 订单
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Order {
private Long id;
private String orderSn;
private Date createTime;
private String receiverAddress;
private Member member;
private List<Product> productList;
}
- Нам нужно преобразовать в
OrderDto
объект,OrderDto
содержитMemberDto
иProductDto
Два подобъекта также необходимо преобразовать;
/**
* 订单Dto
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class OrderDto {
private Long id;
private String orderSn;
private Date createTime;
private String receiverAddress;
//子对象映射Dto
private MemberDto memberDto;
//子对象数组映射Dto
private List<ProductDto> productDtoList;
}
- Нам просто нужно создать интерфейс Mapper, а затем использовать
uses
Внедрите преобразование Mapper дочернего объекта, а затем передайте@Mapping
Установите правила сопоставления атрибутов;
/**
* 订单对象映射
* Created by macro on 2021/10/21.
*/
@Mapper(uses = {MemberMapper.class,ProductMapper.class})
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
@Mapping(source = "member",target = "memberDto")
@Mapping(source = "productList",target = "productDtoList")
OrderDto toDto(Order order);
}
- Далее создаем тестовый интерфейс в Controller, прямо через Mapper
INSTANCE
метод преобразования вызова экземпляраtoDto
;
/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "子对象映射")
@GetMapping("/subMapping")
public CommonResult subMapping() {
List<Order> orderList = getOrderList();
OrderDto orderDto = OrderMapper.INSTANCE.toDto(orderList.get(0));
return CommonResult.success(orderDto);
}
}
- При тестировании интерфейса вызова в Swagger можно обнаружить, что свойства подобъекта были преобразованы.
объединить карту
MapStruct также поддерживает сопоставление нескольких свойств объекта с одним объектом.
- Например здесь
Member
иOrder
Некоторые свойства картыMemberOrderDto
иди в;
/**
* 会员商品信息组合Dto
* Created by macro on 2021/10/21.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberOrderDto extends MemberDto{
private String orderSn;
private String receiverAddress;
}
- Затем добавьте в Mapper
toMemberOrderDto
метод, здесь следует отметить, что, поскольку в параметре два атрибута, вам нужно передать参数名称.属性
имя для указанияsource
для предотвращения конфликтов (в обоих параметрах есть атрибут id);
/**
* 会员对象映射
* Created by macro on 2021/10/21.
*/
@Mapper
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);
@Mapping(source = "member.phone",target = "phoneNumber")
@Mapping(source = "member.birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
@Mapping(source = "member.id",target = "id")
@Mapping(source = "order.orderSn", target = "orderSn")
@Mapping(source = "order.receiverAddress", target = "receiverAddress")
MemberOrderDto toMemberOrderDto(Member member, Order order);
}
- Далее создаем тестовый интерфейс в Controller, прямо через Mapper
INSTANCE
метод преобразования вызова экземпляраtoMemberOrderDto
;
/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "组合映射")
@GetMapping("/compositeMapping")
public CommonResult compositeMapping() {
List<Order> orderList = LocalJsonUtil.getListFromJson("json/orders.json", Order.class);
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
Member member = memberList.get(0);
Order order = orderList.get(0);
MemberOrderDto memberOrderDto = MemberMapper.INSTANCE.toMemberOrderDto(member,order);
return CommonResult.success(memberOrderDto);
}
}
- При тестировании интерфейса вызова в Swagger можно обнаружить, что атрибуты в Member и Order были сопоставлены с MemberOrderDto.
Расширенное использование
С помощью приведенного выше базового использования вы уже можете играть с MapStruct, давайте представим некоторые расширенные возможности.
Использовать внедрение зависимостей
Выше мы все вызываем методы через экземпляр INSTANCE в интерфейсе Mapper, и мы также можем использовать внедрение зависимостей в Spring.
- Чтобы использовать внедрение зависимостей, нам просто нужно
@Mapper
аннотированныйcomponentModel
параметр установлен наspring
Вот и все, поэтому, когда будет сгенерирован класс реализации интерфейса, MapperStruct добавит его@Component
аннотация;
/**
* 会员对象映射(依赖注入)
* Created by macro on 2021/10/21.
*/
@Mapper(componentModel = "spring")
public interface MemberSpringMapper {
@Mapping(source = "phone",target = "phoneNumber")
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
MemberDto toDto(Member member);
}
- Следующее использование в контроллере
@Autowired
Можно использовать инъекцию аннотаций;
/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@Autowired
private MemberSpringMapper memberSpringMapper;
@ApiOperation(value = "使用依赖注入")
@GetMapping("/springMapping")
public CommonResult springMapping() {
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
MemberDto memberDto = memberSpringMapper.toDto(memberList.get(0));
return CommonResult.success(memberDto);
}
}
- При вызове теста интерфейса в Swagger можно обнаружить, что его можно использовать в обычном режиме, как и раньше.
Использование констант, значений по умолчанию и выражений
При использовании MapStruct для сопоставления свойств мы можем установить для свойств константы или значения по умолчанию, либо мы можем автоматически генерировать свойства, написав выражения на Java.
- Например, следующий объект Product класса продукта;
/**
* 商品
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Product {
private Long id;
private String productSn;
private String name;
private String subTitle;
private String brandName;
private BigDecimal price;
private Integer count;
private Date createTime;
}
- Мы хотим преобразовать Product в объект ProductDto,
id
свойства установлены в константы,count
Установите значение по умолчанию на 1,productSn
Установить генерацию UUID;
/**
* 商品Dto
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class ProductDto {
//使用常量
private Long id;
//使用表达式生成属性
private String productSn;
private String name;
private String subTitle;
private String brandName;
private BigDecimal price;
//使用默认值
private Integer count;
private Date createTime;
}
- Создайте
ProductMapper
интерфейс, через@Mapping
в примечанияхconstant
,defaultValue
,expression
Установите правила сопоставления;
/**
* 商品对象映射
* Created by macro on 2021/10/21.
*/
@Mapper(imports = {UUID.class})
public interface ProductMapper {
ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);
@Mapping(target = "id",constant = "-1L")
@Mapping(source = "count",target = "count",defaultValue = "1")
@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
ProductDto toDto(Product product);
}
- Далее создаем тестовый интерфейс в Контроллере, напрямую через интерфейс в
INSTANCE
метод преобразования вызова экземпляраtoDto
;
/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "使用常量、默认值和表达式")
@GetMapping("/defaultMapping")
public CommonResult defaultMapping() {
List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
Product product = productList.get(0);
product.setId(100L);
product.setCount(null);
ProductDto productDto = ProductMapper.INSTANCE.toDto(product);
return CommonResult.success(productDto);
}
}
- При тестировании интерфейса вызова в Swagger объект был успешно преобразован.
Пользовательская обработка до и после сопоставления
MapStruct также поддерживает некоторые пользовательские операции до и после сопоставления, аналогичные аспекту в АОП.
- Поскольку на этом этапе нам нужно создать собственные методы обработки, создайте абстрактный класс
ProductRoundMapper
,пройти через@BeforeMapping
Аннотировать пользовательское сопоставление перед операцией с помощью@AfterMapping
Аннотация после пользовательского сопоставления;
/**
* 商品对象映射(自定义处理)
* Created by macro on 2021/10/21.
*/
@Mapper(imports = {UUID.class})
public abstract class ProductRoundMapper {
public static ProductRoundMapper INSTANCE = Mappers.getMapper(ProductRoundMapper.class);
@Mapping(target = "id",constant = "-1L")
@Mapping(source = "count",target = "count",defaultValue = "1")
@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
public abstract ProductDto toDto(Product product);
@BeforeMapping
public void beforeMapping(Product product){
//映射前当price<0时设置为0
if(product.getPrice().compareTo(BigDecimal.ZERO)<0){
product.setPrice(BigDecimal.ZERO);
}
}
@AfterMapping
public void afterMapping(@MappingTarget ProductDto productDto){
//映射后设置当前时间为createTime
productDto.setCreateTime(new Date());
}
}
- Далее создаем тестовый интерфейс в Controller, прямо через Mapper
INSTANCE
метод преобразования вызова экземпляраtoDto
;
/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "在映射前后进行自定义处理")
@GetMapping("/customRoundMapping")
public CommonResult customRoundMapping() {
List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
Product product = productList.get(0);
product.setPrice(new BigDecimal(-1));
ProductDto productDto = ProductRoundMapper.INSTANCE.toDto(product);
return CommonResult.success(productDto);
}
}
- При тестировании интерфейса вызова в Swagger можно обнаружить, что пользовательская операция была применена.
Обработка исключений карты
Исключения неизбежно будут возникать при выполнении кода, и MapStruct также поддерживает обработку исключений сопоставления.
- Сначала нам нужно создать собственный класс исключений;
/**
* 商品验证异常类
* Created by macro on 2021/10/22.
*/
public class ProductValidatorException extends Exception{
public ProductValidatorException(String message) {
super(message);
}
}
- Затем создайте класс проверки, когда
price
настраивать小于0
При выдаче нашего пользовательского исключения;
/**
* 商品验证异常处理器
* Created by macro on 2021/10/22.
*/
public class ProductValidator {
public BigDecimal validatePrice(BigDecimal price) throws ProductValidatorException {
if(price.compareTo(BigDecimal.ZERO)<0){
throw new ProductValidatorException("价格不能小于0!");
}
return price;
}
}
- После этого проходим
@Mapper
аннотированныйuses
Класс проверки использования атрибута;
/**
* 商品对象映射(处理映射异常)
* Created by macro on 2021/10/21.
*/
@Mapper(uses = {ProductValidator.class},imports = {UUID.class})
public interface ProductExceptionMapper {
ProductExceptionMapper INSTANCE = Mappers.getMapper(ProductExceptionMapper.class);
@Mapping(target = "id",constant = "-1L")
@Mapping(source = "count",target = "count",defaultValue = "1")
@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
ProductDto toDto(Product product) throws ProductValidatorException;
}
- Затем добавьте тестовый интерфейс в контроллер, установите
price
за-1
, при сопоставлении будет выдано исключение;
/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "处理映射异常")
@GetMapping("/exceptionMapping")
public CommonResult exceptionMapping() {
List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
Product product = productList.get(0);
product.setPrice(new BigDecimal(-1));
ProductDto productDto = null;
try {
productDto = ProductExceptionMapper.INSTANCE.toDto(product);
} catch (ProductValidatorException e) {
e.printStackTrace();
}
return CommonResult.success(productDto);
}
}
- При тестировании интерфейса вызова в Swagger было обнаружено, что информация о пользовательском исключении была напечатана в рабочем журнале.
Суммировать
Благодаря опыту использования MapStruct, описанному выше, мы можем обнаружить, что MapStruct намного мощнее, чем BeanUtils. Когда мы хотим реализовать более сложное сопоставление объектов, это может сэкономить процесс написания методов Getter и Setter. Конечно, вышеизложенное представляет только некоторые общие функции MapStruct.Его функций гораздо больше.Заинтересованные друзья могут проверить официальные документы.
использованная литература
Официальная документация:карта struct.org/document ATI…
Адрес исходного кода проекта
Эта статьяGitHubGitHub.com/macro-positive/…Он был записан, приветствую всех на Star!