概述
MapStruct 是一个注解处理器,,用于在编译期间生成类型安全且高性能的无依赖的 Java Bean 映射代码。它主要用于实现不同类型之间的互相映射,解决对象拷贝问题,避免手动编写繁琐的映射代码。
常见的动态映射
在 Java 中,常见的动态映射方式有 Apache BeanUtils、Spring BeanUtils 和 cglib.BeanCopier 等。这些工具在一定程度上可以实现对象之间的映射,但由于它们主要采用反射机制,性能较低且不够类型安全。
使用
在使用MapStruct时,只需要定义一个映射器接口,接口中声明任何映射方法。在编译期间,MapStruct 将生成此接口的实现。此实现使用纯 Java 方法调用来映射源对象和目标对象,即没有反射。
1、添加依赖
<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
2、数据准备
@Data
@ToString
@AllArgsConstructor
public class PersonDTO {
private String name;
private String sex;
private Integer age;
}
@Data
@ToString
public class PersonVO {
private String name;
private String sex;
}
3、定义映射器
现在需要将Dto装为vo,需要定义映射器
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
PersonVO dtoToVo(PersonDTO dto);
}
4、测试
PersonDTO dto = new PersonDTO("阿劲", "男", 24);
PersonVO vo = PersonMapper.INSTANCE.dtoToVo(dto);
System.out.println(vo);
5、映射不同字段名的字段
MapStruct 能够自动映射我们的 bean,因为它们具有相同的字段名称。那么,如果我们要映射的 bean 具有不同的字段名称怎么办?
public class StudentDTO {
private String nickname;
private String sex;
}
@Data
@ToString
public class StudentVO {
private String name;
private String sex;
}
定义映射器
当映射不同的字段名称时,我们需要将其源字段配置为其目标字段,为此,我们需要添加*@Mappings注释。这个注解接受一个@Mapping*注解的数组,我们将使用它来添加目标和源属性
@Mapper
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
@Mapping(target = "voName", source = "nickname") // 属性名不一致
@Mapping(target = "sex", ignore = true) // 排除
StudentVO dtoToVo(StudentDTO dto);
}
6、具有多个源参数的映射方法
MapStruct 支持具有多个源参数的映射方法。例如,将多个实体组合成一个数据传输对象
-
数据
@Data @AllArgsConstructor public class ClassDTO { private String name; private Integer count; } @Data @ToString @AllArgsConstructor public class StudentDTO { private String name; private String sex; private Integer age; } @Data @ToString public class StudentVO { private String studentName; private String sex; private Integer age; private String className; } -
定义映射器
@Mapper public interface StudentMapper { StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class); @Mapping(source = "studentDTO.name", target = "studentName") @Mapping(source = "classDTO.name", target = "className") StudentVO dtoToVo(StudentDTO studentDTO, ClassDTO classDTO); } -
测试
public class Test { public static void main(String[] args) { StudentDTO studentDTO = new StudentDTO("阿劲", "男", 24); ClassDTO classDTO = new ClassDTO("非师一班", 80); StudentVO vo = StudentMapper.INSTANCE.dtoToVo(studentDTO, classDTO); System.out.println(vo); } }
7、表达式
/**
* expression 表达式
* 两种写法
* 全限定类名加方法
* @Mapping(target = "no", expression = "java(mapper.utils.NumberUtil.init())")
* 调用方法
* @Mapping(target = "no", expression = "java(getNo())")
*
* 如果是spring注入的
* @Autowired
* NumberUtil numberUtil;
* @Mapping(target = "no", expression = "java(numberUtil.init())")
*
*
* 导入
* @Mapper(classes = NumberUtil.class)
* @Mapping(target = "no", expression = "java(NumberUtil.getNo())")
* StudentVO dtoToVo1(StudentDTO studentDTO, ClassDTO classDTO);
* @param studentDTO
* @param classDTO
* @return
*/
@Mapping(target = "no", expression = "java(getNo())")
StudentVO dtoToVo1(StudentDTO studentDTO, ClassDTO classDTO);
/**
* 获取编号
* @return
*/
default String getNo() {
return NumberUtil.init();
}
8、更多用法
原理
MapStruct 生成的代码, 类似于自己写的一样,不过是编译器自动生成,生成的代码可以在编译后看到, 在 target/generated-sources/annotations 下。 同时真正在代码包执行的可以在target/classes包中看到。
总结
MapStruct主要用来解决对象拷贝,它区别与BeanUtils是通过反射进行属性赋值转换,他是通过编译器生成常规的方法,因此相对于反射,速度更快,效率更高,同时提供了多种配置进行字段名不同映射,多个数据源映射,自定义方法等等。