前言
在我们的实际编码工作中会碰到很多对象转化的场景,尤其像DO、DTO、VO等对象的转化非常多,通常我们会选择第三方工具或者自己手写,但他们或多或少都有一些自己的缺点。下面详细说说常用的方案和最佳方案,还有各种方案之间的耗时数据比较。
常用方案
手写
public class TeacherConvertor implements ConvertorI<TeacherEntity, TeacherDO> {
/**
* 对象转化
*
* @param req
* @return
*/
public static TeacherEntity dataToEntity(TeacherDO req){
if (req == null) {
return null;
}
TeacherEntity result = new TeacherEntity();
result.setAddress(req.getAddress());
result.setAge(req.getAge());
result.setName(req.getName());
result.setSex(req.getSex());
return result;
}
/**
* 对象转化
*
* @param req
* @return
*/
public static TeacherDO entityToData(TeacherEntity req) {
if (req == null) {
return null;
}
TeacherDO result = new TeacherDO();
result.setAddress(req.getAddress());
result.setAge(req.getAge());
result.setName(req.getName());
result.setSex(req.getSex());
return result;
}
}
这种方案的缺点是需要手写很多繁琐的代码,一不小心眼花了还容易设置错了字段
apache.BeanUtils
org.apache.commons.beanutils.BeanUtils.copyProperties(do, entity);
这种方案因为用到反射的原因,同时本身设计问题,性能比较差,其次针对复杂场景支持能力不足
spring.BeanUtils
org.springframework.beans.BeanUtils.copyProperties(do, entity);
这种方案针对apache的BeanUtils做了很多优化,整体性能提升不少,不过还是比不上原生代码处理,其次针对复杂场景支持能力不足
PropertyUtils
PropertyUtils.copyProperties(do, entity);
这种方案因为用到反射的原因,性能比手写要差,其次针对复杂场景支持能力不足
beanCopier
BeanCopier copier = BeanCopier.create(TeacherDO.class, TeacherEntity.class, false);
copier.copy(do, entity, null);
这种方案动态生成一个要代理类的子类,其实就是通过字节码方式转换成性能最好的get和set方式,重要的开销在创建BeanCopier,整体性能接近原生代码处理,但是针对复杂场景支持能力不足
fastjson
TeacherEntity entity = JSON.parseObject(JSON.toJSONString(teacherDO), TeacherEntity.class);
这种方案因为通过生成中间json格式字符串,然后再转化成目标对象,性能比较差,同时因为中间会生成json格式字符串,如果转化过多,gc会非常频繁,同时针对复杂场景支持能力不足
最佳方案
mapstruct
mapstruct方案的性能和原生代码一样,而且代码量还少,另外相对手写方案来说不容易出错,同时支持各种复杂转化场景
使用方式
如上手写方案换成mapstruct的话
convertor声明
@Mapper
public interface TeacherMapper {
TeacherMapper MAPPER = Mappers.getMapper( TeacherMapper.class );
TeacherEntity dataToEntity(TeacherDO req);
TeacherDO entityToData(TeacherEntity req);
}
convertor使用
TeacherEntity entity = TeacherMapper.MAPPER.dataToEntity(do);
原理分析
mapstruct的原理和lombok差不多,都是通过编译时注解处理器来实现的。在编译的时候识别注解,然后生成一些类和代码。
如上我们申明了一个TeacherMapper。在编译之后会多生成一个TeacherMapperImpl类
里面的代码和我们手写的TeacherConvertor代码基本一样
在使用中用到的TeacherMapper.MAPPER其实就是这个TeacherMapperImpl类。
更多支持场景
- 字段不同名转化
@Mapper
public interface TeacherMapper {
TeacherMapper MAPPER = Mappers.getMapper( TeacherMapper.class );
@Mapping(source = "address", target = "addressInfo")
TeacherEntity dataToEntity(TeacherDO req);
TeacherDO entityToData(TeacherEntity req);
}
- 简单对象转化
- 集合类型转化
- 对象嵌套转化
- N个对象转一个对象
- 转化能力自定义扩展
- 还有更多功能可以去github上了解下
性能比较
测试代码
public class MainTest {
/**
* 转化对象
*/
private TeacherDO teacherDO;
/**
* 转化次数
*/
private final static int count = 1000000;
@Before
public void before() {
teacherDO = new TeacherDO("name", "sex", "address", "age");
}
@Test
public void userCopy() {
long startTime = System.currentTimeMillis();
for (int i = 1; i <= count; i++) {
// log.debug(i+"");
TeacherEntity entity = TeacherConvertor.dataToEntity(teacherDO);
}
System.out.println("userCopy time" + (System.currentTimeMillis() - startTime));
}
@Test
public void mapstruct() {
long startTime = System.currentTimeMillis();
for (int i = 1; i <=count; i++) {
// log.debug(i+"");
TeacherEntity entity = TeacherMapper.MAPPER.dataToEntity(teacherDO);
}
System.out.println("mapstruct time" + (System.currentTimeMillis() - startTime));
}
@Test
public void beanCopier() {
BeanCopier copier = BeanCopier.create(TeacherDO.class, TeacherEntity.class, false);
long startTime = System.currentTimeMillis();
for (int i = 1; i <= count; i++) {
// log.debug(i+"");
TeacherEntity entity = new TeacherEntity();
copier.copy(teacherDO, entity, null);
}
System.out.println("beanCopier time" + (System.currentTimeMillis() - startTime));
}
@Test
public void springBeanUtils(){
long startTime = System.currentTimeMillis();
for (int i = 1; i <=count; i++) {
// log.debug(i+"");
TeacherEntity entity = new TeacherEntity();
org.springframework.beans.BeanUtils.copyProperties(teacherDO, entity);
}
System.out.println("springBeanUtils time" + (System.currentTimeMillis() - startTime));
}
@Test
public void apacheBeanUtils() throws InvocationTargetException, IllegalAccessException {
long startTime = System.currentTimeMillis();
for (int i = 1; i <=count; i++) {
// log.debug(i+"");
TeacherEntity entity = new TeacherEntity();
org.apache.commons.beanutils.BeanUtils.copyProperties(teacherDO, entity);
}
System.out.println("apacheBeanUtils time" + (System.currentTimeMillis() - startTime));
}
@Test
public void apachePropertyUtils() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
long startTime = System.currentTimeMillis();
for (int i = 1; i <= count; i++) {
// log.debug(i+"");
TeacherEntity entity = new TeacherEntity();
PropertyUtils.copyProperties(teacherDO, entity);
}
System.out.println("apachePropertyUtils time" + (System.currentTimeMillis() - startTime));
}
@Test
public void fastjson() {
long startTime = System.currentTimeMillis();
for (int i = 1; i <= count; i++) {
// log.debug(i+"");
TeacherEntity entity = JSON.parseObject(JSON.toJSONString(teacherDO), TeacherEntity.class);
}
System.out.println("fastjson time" + (System.currentTimeMillis() - startTime));
}
}
测试结果
tools/count | 1000/次 | 10000/次 | 100000/次 | 1000000/次 |
---|---|---|---|---|
手写 | 1ms | 2ms | 7ms | 14ms |
mapstruct | 3ms | 5ms | 10ms | 11ms |
beanCopier | 1ms | 3ms | 11ms | 14ms |
apachePropertyUtils | 26ms | 99ms | 335ms | 2438ms |
springBeanUtils | 91ms | 108ms | 180ms | 511ms |
apacheBeanUtils | 107ms | 213ms | 598ms | 4022ms |
fastjson | 146ms | 274ms | 402ms | 653ms |
网友评论