MapStruct的使用
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<PartDto> 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<PartDto> 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<PartDto> partDtoList = new ArrayList<>();
partDtoList.add(new PartDto(3,"轮胎"));
carDto.setPartList(partDtoList);
return carDto;
}
}
可以看到成功赋值。但是这种代码太多,如果写在业务层不能突出业务逻辑的重点,那么有没有一种快速的赋值方法呢?我们可以使用MapStruct进行快速赋值
使用MapStruct
首先,需要导入MapStruct的jar包
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Final</version>
</dependency>
其次,编写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<PartDto> 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<PartDto> 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
在映射最后一步对属性的自定义映射处理