1. 对象拷贝中常用的工具类
1.1 Apache BeanUtils#copyProperties
特点:
浅拷贝,属性还是指向原本对象的引用
字段名称相同,类型不同无法进行赋值
基本类型字段和引用对象可以映射
//对象拷贝:将第一个参数对象,拷贝到第二个参数的对象中,第三个参数表示忽略拷贝的内容
BeanUtils.copyProperties(dishPage,dishDtoPage,"records");
1.2 SpringUtils#copyProperties
特点:
特性同Apache,效率比Apache高
参数位置同Apache不同
//对象拷贝:将第一个参数对象,拷贝到第二个参数的对象中,第三个参数表示忽略拷贝的内容
BeanUtils.copyProperties(dishPage,dishDtoPage,"records");
1.3 序列化(JSON)
特点:
性能较低,耗时(序列化-反序列化)
深拷贝
基本类型字段和引用对象可以映射
字段名称相同,类型不同可以赋值(如Long -> String)
1.4 MapStruct(推荐)
特点:
灵活(可自主配置字段映射关系)
可以配置多对一映射关系
效率高,准确(编译器代码生成,源码就是get、set方法)
深拷贝
如果你使用Maven的话,可以通过引入依赖安装MapStruct:
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
这个依赖项会导入MapStruct的核心注释。由于MapStruct在编译时工作,并且会集成到像Maven和Gradle这样的构建工具上,我们还必须在<build中/>标签中添加一个插件maven-compiler-plugin
,并在其配置中添加annotationProcessorPaths
,该插件会在构建时生成对应的代码。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
情况一:相同字段间的映射
我们先从一些基本的映射开始。我们会创建一个Doctor对象和一个DoctorDto。为了方便起见,它们的属性字段都使用相同的名称:
public class Doctor {
private int id;
private String name;
// getters and setters or builder
}
public class DoctorDto {
private int id;
private String name;
// getters and setters or builder
}
现在,为了在这两者之间进行映射,我们要创建一个DoctorMapper
接口。对该接口使用@Mapper
注解,MapStruct就会知道这是两个类之间的映射器。
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
DoctorDto toDto(Doctor doctor);
}
这段代码中创建了一个DoctorMapper
类型的实例INSTANCE
,在生成对应的实现代码后,这就是我们调用的“入口”。
我们在接口中定义了toDto()
方法,该方法接收一个Doctor
实例为参数,并返回一个DoctorDto
实例。这足以让MapStruct知道我们想把一个Doctor
实例映射到一个DoctorDto
实例。
当我们构建/编译应用程序时,MapStruct注解处理器插件会识别出DoctorMapper接口并为其生成一个实现类。
public class DoctorMapperImpl implements DoctorMapper {
@Override
public DoctorDto toDto(Doctor doctor) {
if ( doctor == null ) {
return null;
}
DoctorDtoBuilder doctorDto = DoctorDto.builder();
doctorDto.id(doctor.getId());
doctorDto.name(doctor.getName());
return doctorDto.build();
}
}
DoctorMapperImpl
类中包含一个toDto()
方法,将我们的Doctor
属性值映射到DoctorDto
的属性字段中。如果要将Doctor
实例映射到一个DoctorDto
实例,可以这样写:
DoctorDto doctorDto = DoctorMapper.INSTANCE.toDto(doctor);
情况二:不同字段间的映射
public class Doctor {
private int id;
private String name;
private String specialty;
// getters and setters or builder
}
public class DoctorDto {
private int id;
private String name;
private String specialization;
// getters and setters or builder
}
现在,我们需要让 DoctorMapper
知道这里的不一致。我们可以使用 @Mapping
注解,并设置其内部的 source
和 target
标记分别指向不一致的两个字段。
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
@Mapping(source = "doctor.specialty", target = "specialization")
DoctorDto toDto(Doctor doctor);
}
这个注解代码的含义是:Doctor
中的specialty
字段对应于DoctorDto
类的 specialization
。
编译之后,会生成如下实现代码:
public class DoctorMapperImpl implements DoctorMapper {
@Override
public DoctorDto toDto(Doctor doctor) {
if (doctor == null) {
return null;
}
DoctorDtoBuilder doctorDto = DoctorDto.builder();
doctorDto.specialization(doctor.getSpecialty());
doctorDto.id(doctor.getId());
doctorDto.name(doctor.getName());
return doctorDto.build();
}
}