Убей БинУтилс! Попробуйте этот инструмент автоматического сопоставления Bean, он действительно мощный!

Spring Boot Java
Убей БинУтилс! Попробуйте этот инструмент автоматического сопоставления Bean, он действительно мощный!

При работе над проектом часто необходимо конвертировать между 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);
}
  • Создайте тестовый интерфейс в Контроллере, напрямую через интерфейс MapperINSTANCEметод преобразования вызова экземпляра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, прямо через MapperINSTANCEметод преобразования вызова экземпляра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;
}
  • Затем добавьте в MappertoMemberOrderDtoметод, здесь следует отметить, что, поскольку в параметре два атрибута, вам нужно передать参数名称.属性имя для указания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, прямо через MapperINSTANCEметод преобразования вызова экземпляра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, прямо через MapperINSTANCEметод преобразования вызова экземпляра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…

Адрес исходного кода проекта

GitHub.com/macro-positive/…

Эта статьяGitHubGitHub.com/macro-positive/…Он был записан, приветствую всех на Star!