对象映射大体分为两种:
MapStruct简化Bean对象之间的映射处理,其实质就是set/get的自动处生成转换理,参考官网:
导入依赖包
compile('org.mapstruct:mapstruct-jdk8:1.2.0.Final')
annotationProcessor("org.mapstruct:mapstruct-processor:1.2.0.Final")
如果引入以上包后编译无法生效,需要导入以下配置:
plugins {
id 'net.ltgt.apt' version '0.15'
}
apply plugin: 'net.ltgt.apt-idea'
apply plugin: 'net.ltgt.apt-eclipse'
/**
* 配置mapstruct任务
* http://mapstruct.org/documentation/dev/reference/html/#_gradle
*/
tasks.withType(JavaCompile) {
options.compilerArgs = [
'-Amapstruct.suppressGeneratorTimestamp=true',
//默认交由spring管理,在外部可以直接使用@Autowired注入mapper
'-Amapstruct.defaultComponentModel=spring',
'-Amapstruct.suppressGeneratorVersionInfoComment=true',
'-Amapstruct.unmappedTargetPolicy=IGNORE'
]
}
定义数据源对象
public class PersonDto {
private String nameDto;
private int age;
private String address;
private String idno;
private double money;
private Date birthDate;
}
定义目标对象
public class Person {
private String name;
private int age;
private String address;
private String idno;
private double money;
private Date birthDate;
}
注意:其中name属性和PersonDto的nameDto不是同名
定义源、目标对象映射关联接口
@Mapper(componentModel = "spring")
public interface PersonMapStruct {
PersonMapStruct INSTANCE = Mappers.getMapper(PersonMapStruct.class);
@Mappings({
@Mapping(source = "nameDto", target = "name"),
// @Mapping(target = "birthDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(personDto.getBirthDate(),\"yyyy-MM-dd\"))")
})
Person dtoToPoByPersonDto(PersonDto personDto);
}
注意:
如果属性名称完全相同则不做处理,相反不同属性名称之间的映射可以使用@Mapping(source = “nameDto”, target = “name”)
可以使用一些高级特性,如expression属性可以定义java代码用以进行特殊处理
componentModel属性用于指定自动生成的接口实现类的组件类型
这个属性支持四个值:
default: 这是默认的情况,mapstruct不使用任何组件类型, 可以通过Mappers.getMapper(Class)方式获取自动生成的实例对象。
cdi: the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject
spring: 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的 @Autowired方式进行注入
jsr330: 生成的实现类上会添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取。
编译完成后会生成接口的实现类
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-01-28T17:18:38+0800",
comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_131 (Oracle Corporation)"
)
@Component
public class PersonMapStructImpl implements PersonMapStruct {
@Override
public Person dtoToPoByPersonDto(PersonDto personDto) {
if ( personDto == null ) {
return null;
}
Person person = new Person();
person.setName( personDto.getNameDto() );
person.setAge( personDto.getAge() );
person.setAddress( personDto.getAddress() );
person.setIdno( personDto.getIdno() );
person.setMoney( personDto.getMoney() );
person.setBirthDate( personDto.getBirthDate() );
return person;
}
}
注意:根据生成在target包下的源码可看出, mapstruct实则是生成了一些set/get方法,且对一些数据进行转换处理等
Person person = PersonMapStruct.INSTANCE.dtoToPoByPersonDto(personDto);
针对几种常见的Bean复制方式,分别进行简单的性能测试得出(单位:ms):
10次测验 | 第一次 | 第二次 | 第三次 | 平均值 | 每次平均值 |
---|---|---|---|---|---|
BeanUtil.copyProperties | 131 | 130 | 137 | 132.667 | 13.2667 |
PropertyUtils.copyProperties | 78 | 104 | 87 | 89.667 | 8.9667 |
spring.BeanUtils.copyProperties | 1 | 1 | 1 | 1 | 0.1 |
BeanCopier.create | 0 | 0 | 1 | 0.3333 | 0.0333 |
MapStruct | 3 | 3 | 3 | 3 | 0.3 |
10000次测验 | 第一次 | 第二次 | 第三次 | 平均值 | 每次平均值 |
---|---|---|---|---|---|
BeanUtil.copyProperties | 235 | 231 | 203 | 223 | 22.3 |
PropertyUtils.copyProperties | 721 | 662 | 625 | 429.3092 | 42.9309 |
spring.BeanUtils.copyProperties | 11 | 11 | 11 | 11 | 0.11 |
BeanCopier.create | 2 | 2 | 3 | 2.3333 | 0.2333 |
MapStruct | 5 | 5 | 9 | 6.3333 | 0.6333 |
100000次测验 | 第一次 | 第二次 | 第三次 | 平均值 | 每次平均值 |
---|---|---|---|---|---|
BeanUtil.copyProperties | 500 | 570 | 572 | 547.3333 | 54.7333 |
PropertyUtils.copyProperties | 3572 | 2475 | 3796 | 3281 | 328.1 |
spring.BeanUtils.copyProperties | 58 | 55 | 51 | 54.6667 | 5.4667 |
BeanCopier.create | 4 | 3 | 4 | 3.6667 | 0.3667 |
MapStruct | 10 | 11 | 9 | 10 | 1 |
注:内存12G、四核i7-4720QU处理器
根据测试结果得出,Cglib在测试中的优势较为明显,这得益于它的缓存机制,大家可以看下java.lang.reflect.WeakCache类,不过即便没有缓存,性能依旧是不错,几乎零消耗;PropertyUtils的结果随着调用增长,呈迅速增长趋势,相对spring的copy则是稳步增长,最后是MapStruct则相对平稳且相对来说很稳健,Cglib基于类反射代理,只拷贝名称和类型都相同的属性,所以当目标类的setter数目比getter少时,创建BeanCopier会失败而导致拷贝不成功,需要手动去处理
综上所述,相对来说,实现Bean拷贝的自动化处理,且侵入较小,性能也保证的前提下,Cglib和MapStruct都是不错的选择,不过对于更好的自定义和可读性来说,推荐MapStruct。