MapStruct的原理与使用

MapStruct的使用

  • 1、MapStruct是什么
  • 2、MapStruct与BeanUtils有什么区别
  • 3、怎么使用MapStruct
    • 整体结构
    • Dto
      • CarDto类
      • PartDto类
      • PersonDto类
      • Vo
        • CarVo类
        • PersonVo类
        • 传统方法
        • 使用MapStruct
        • 4、总结与原理
          • @Mapper默认映射规则
          • @AfterMapping和@MappingTarget

            1、MapStruct是什么

            MapStruct是一个Java注释处理器(annotation processor),用于自动生成类型安全的Java Bean映射器,它可以轻松地将一个Java Bean类型的数据转换为另一个Java Bean类型的数据。

            在Java应用程序中,我们通常需要将一个对象转换为另一个对象。这个过程需要手动编写代码,需要大量的时间和精力,而且容易出错。MapStruct的目标就是简化这个过程,通过注释处理器来自动生成转换代码,从而节省开发时间和减少错误。

            MapStruct支持注释处理器,可以在编译时生成类型安全的转换代码。它使用注释来指定数据映射的细节和规则,然后自动生成对应的Java Bean映射器代码。MapStruct生成的代码是类型安全的,因为它使用了Java编译器的类型检查机制。

            使用MapStruct可以大大提高代码的可读性和可维护性,同时也可以提高应用程序的性能。MapStruct是一个开源项目,可以在GitHub上找到它的源代码和文档。

            2、MapStruct与BeanUtils有什么区别

            MapStruct和BeanUtils都是用于Java Bean之间的转换,但它们有以下几个区别:

            1、MapStruct是基于注解处理器的代码生成器,它在编译时生成类型安全的转换代码。而BeanUtils是运行时使用反射进行转换,没有生成代码的过程,因此性能较低。

            2、MapStruct的生成代码是类型安全的,不会出现编译时错误,而BeanUtils则需要在运行时才能发现错误。

            3、MapStruct可以通过注解控制数据映射的方式,包括字段名称、字段类型、格式转换等,而BeanUtils只能复制相同名称的属性。

            4、MapStruct的注解方式比BeanUtils更加直观,易于理解和维护。

            5、MapStruct支持复杂类型转换,例如集合和嵌套对象之间的转换,而BeanUtils只支持简单类型转换。

            综上所述,MapStruct相对于BeanUtils有更好的性能、类型安全和可维护性,而且支持复杂类型转换,因此在Java Bean之间的转换中更加适合使用。

            3、怎么使用MapStruct

            首先,我们来看一个需求:

            业务层处理了一个需求,并把处理结果存在CarDto中,现在要把它转化为CarVo传给前端,请问怎么把CarDto快速转化为CarVo?

            整体结构

            Dto

            CarDto类

            /**
             * 车辆Dto
             */
            @Data
            public class CarDto { /**
                 * 车辆id
                 */
                private int id;
                /**
                 * 车辆名字
                 */
                private String name;
                /**
                 * 车辆价格
                 */
                private double price;
                /**
                 * 车辆数量
                 */
                private int num;
                /**
                 * 车辆部件
                 */
                private List partList;
                /**
                 * 驾驶人
                 */
                private PersonDto personDto;
            }
            

            PartDto类

            /**
             * 车辆零部件Dto
             */
            @Data
            @AllArgsConstructor
            @NoArgsConstructor
            public class PartDto { /**
                 * 零部件id
                 */
                private int partId;
                /**
                 * 零部件名字
                 * 
                 */
                private String partName;
            }
            

            PersonDto类

            /**
             * 驾驶人Dto
             */
            @Data
            @AllArgsConstructor
            @NoArgsConstructor
            public class PersonDto { /**
                 * 驾驶人id
                 */
                private int personId;
                /**
                 * 驾驶人名字
                 */
                private String  personName;
            }
            

            Vo

            CarVo类

             * 车辆Vo
             */
            @Data
            public class CarVo { /**
                 * 车辆id
                 */
                private Integer id;
                /**
                 * 车辆名字
                 */
                private String name;
                /**
                 * 车辆价格
                 */
                private String price;
                /**
                 * 车辆数量
                 */
                private Integer carNum;
                /**
                 * 车辆是否存在零部件
                 */
                private Boolean isPart;
                /**
                 * 驾驶人
                 */
                private PersonVo personVo;
            }
            

            PersonVo类

            /**
             * 驾驶人Vo
             */
            @Data
            public class PersonVo { /**
                 * 驾驶人id
                 */
                private Integer id;
                /**
                 * 驾驶人名字
                 */
                private String name;
            }
            

            传统方法

            对于这个需求,我们通常想到的方法是把CarDto中的属性一个一个赋值给CarVo

            public class Test1 { public static void main(String[] args) { CarDto carDto = getCarDto();
                    CarVo carVo = new CarVo();
                    // 批量把CarDto赋值给CarVo
                    carVo.setId(carDto.getId());
                    carVo.setName(carDto.getName());
                    carVo.setPrice(String.valueOf(carDto.getPrice()));
                    carVo.setCarNum(carDto.getNum());
                    // 判断CarDto的零部件是否有值
                    List dtoPartList = carDto.getPartList();
                    boolean isPart=(dtoPartList!=null&&dtoPartList.size()!=0);
                    carVo.setIsPart(isPart);
                    // 把CartDto的驾驶人赋值给CarVO
                    PersonDto personDto = carDto.getPersonDto();
                    PersonVo personVo = new PersonVo();
                    personVo.setId(personDto.getPersonId());
                    personVo.setName(personDto.getPersonName());
                    carVo.setPersonVo(personVo);
                    // 输出
                    System.out.println(carDto);
                    System.out.println(carVo);
                }
                // 获得对应的CarDto
                private static CarDto getCarDto(){ CarDto carDto = new CarDto();
                    carDto.setId(1);
                    carDto.setName("丰田");
                    carDto.setPrice(4.20);
                    carDto.setNum(50);
                    carDto.setPersonDto(new PersonDto(10,"张三"));
                    ArrayList partDtoList = new ArrayList<>();
                    partDtoList.add(new PartDto(3,"轮胎"));
                    carDto.setPartList(partDtoList);
                    return carDto;
                }
            }
            

            可以看到成功赋值。但是这种代码太多,如果写在业务层不能突出业务逻辑的重点,那么有没有一种快速的赋值方法呢?我们可以使用MapStruct进行快速赋值

            使用MapStruct

            首先,需要导入MapStruct的jar包

             org.mapstruct mapstruct 1.2.0.Final   org.mapstruct mapstruct-processor 1.2.0.Final 

            其次,编写CarConvert转换接口,本接口中所使用到的注解都是org.mapstruct包下的

            @Mapper
            public interface CarConvert { CarConvert INSTANCE=Mappers.getMapper(CarConvert.class);
                /**
                 * Mapping注解的参数解释:
                 *      1、source:源属性名称,即CarDto类下的属性名称
                 *      2、target:目标属性名称,即CarVo类下的属性名称
                 */
                @Mappings({ @Mapping(source = "num",target = "carNum"),
                        @Mapping(source = "personDto",target = "personVo")
                })
                public CarVo getCarVo(CarDto carDto);
                /**
                 * 指定PersonDto转换为PersonVo
                 */
                @Mappings({ @Mapping(source = "personId",target = "id"),
                        @Mapping(source = "personName",target = "name")
                })
                public PersonVo personDtoToPersonVo(PersonDto personDto);
                /**
                 * 在映射的最后一步对属性的自定义映射处理
                 */
                @AfterMapping //表示让MapStruct在调用完自动转换方法后,回来自动调用本方法
                public default void dtoVoAfter(CarDto carDto,@MappingTarget CarVo carVo){ // @MappingTarget : 表示传来的carVO对象是已经赋过值的
                    // 判断CarDto的零部件是否有值
                    List dtoPartList = carDto.getPartList();
                    boolean isPart=(dtoPartList!=null&&dtoPartList.size()!=0);
                    carVo.setIsPart(isPart);
                }
            }
            

            编写测试类

            public class Test2 { public static void main(String[] args) { CarDto carDto = getCarDto();
                    // 根据你配置的映射规则把CarDto转化为CarVO
                    CarVo carVo = CarConvert.INSTANCE.getCarVo(carDto);
                    // 输出
                    System.out.println(carDto);
                    System.out.println(carVo);
                }
                // 获得对应的CarDto
                private static CarDto getCarDto(){ CarDto carDto = new CarDto();
                    carDto.setId(1);
                    carDto.setName("丰田");
                    carDto.setPrice(4.20);
                    carDto.setPersonDto(new PersonDto(10,"张三"));
                    carDto.setNum(50);
                    ArrayList partDtoList = new ArrayList<>();
                    partDtoList.add(new PartDto(3,"轮胎"));
                    carDto.setPartList(partDtoList);
                    return carDto;
                }
            }
            

            可以看到同样转换成功

            4、总结与原理

            @Mapper默认映射规则

            1、同类型且同名的属性,会自动映射

            2、MapStruct会自动进行类型转换

            ① 8种基本类型和太慢对应的包装类型之间

            ② 8种基本类型(包括它们的包装类型)和String之间

            ③ 日期类型和String之间

            3、source或target多余的属性对方没有,不会报错

            4、属性是应用对象的映射处理:多加一个接口方法进行处理

            @AfterMapping和@MappingTarget

            在映射最后一步对属性的自定义映射处理