关键知识点提炼:
-
BeanUtils.copyProperties的使用场景:
DO、DTO、VO之间的转换 BeanUtils.copyProperties的避坑
- BeanUtils的
替代工具类——
Mapstruct
一、BeanUtils.copyProperties的
使用场景
涉及到的DO、DTO、VO之间的转换,我们大多使用Spring框架里的BeanUtils.copyProperties
来做对象转换,以下举例对比使用工具和不使用工具:
DTO和VO的转换
@Data
public class OrderDTO {
private long id;
private Long userId;
private String orderNo;
private Date gmtCreated;
}
@Data
public class OrderVO {
private long id;
private long userId;
private String orderNo;
private Date gmtCreated;
}
不使用工具转换:
public static void main(String[] args) {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setId(1L);
orderDTO.setUserId(123L);
orderDTO.setOrderNo("20210518000001");
orderDTO.setGmtCreated(new Date());
OrderVO orderVO = new OrderVO();
orderVO.setId(orderDTO.getId());
orderVO.setUserId(orderDTO.getUserId());
orderVO.setOrderNo(orderDTO.getOrderNo());
orderVO.setGmtCreated(orderDTO.getGmtCreated());
}
使用工具转换:
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orderDTO, orderVO);
//使用自定义工具---如下
OrderVO orderVO=BeanUtil.copyProperties(OrderVO.class, orderDTO);
以下是自定义的一个工具类:
- BeanUtil(包装BeanUtils)
package com.test.utils;
import java.util.*
import org.springframword.beans.Beanutils;
import org.springframword.util.CollectionUtils;
public class BeanUtil extends BeanUtils(
public BeanUtil(){
}
public statci<T, E> List<T> Dto2List(Class<T> destType, List<E> orig){
List<T> dest = new ArratList();
if (CollectionUtils.isEmpty(orig)){
return dest;
}else {
Iterator it = orig.iterator();
while(it.hasNext()){
E e = it.next();
T obj = BeanUtils.instantiateClase(destType);
BeanUtils.copyProperties(e, obj);
dest.add(obj);
}
return dest;
}
}
public statci<T> T copyProperties(Class<T> destType, Object srcObject){
if (srcObject == null){
return null;
} else {
T obj = BeanUtils.instantiateClase(destType);
BeanUtils.copyProperties(srcObject, obj);
return obj;
}
}
二、BeanUtils.copyProperties的避坑
使用该方法是一种浅拷贝,会有坑:
1、包装类型转基本类型问题
例如OrderDTO的Long类型转换成OrderDO的long时,会抛java.lang.IllegalArgumentException
异常
2、空格问题
OrderDTO的字段A带前后空格,转换成OrderDO仍然会有空格,造成脏数据。
3、查不到饮用字段。
三、
BeanUtils的替代工具类——
Mapstruct
先上结论,Mapstruct的性能远远高于BeanUtils:
对象转换次数 | 属性个数 | BeanUtils耗时 | Mapstruct耗时 |
5千万次 | 6 | 14秒 | 1秒 |
5千万次 | 15 | 36秒 | 1秒 |
5千万次 | 25 | 55秒 | 1秒 |
1、Mapstruct 依赖
使用Mapstruct需要依赖的包如下,mapstruct、mapstruct-processor、lombok,可以去仓库中查看最新版本。
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
2、Mapstruct的属性拷贝:
UserPo和UserEntity的属性类型和名称完全相同。
package mapstruct;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserPo {
private Long id;
private Date gmtCreate;
private Date createTime;
private Long buyerId;
private Long age;
private String userNick;
private String userVerified;
}
package mapstruct;
import lombok.Data;
import java.util.Date;
@Data
public class UserEntity {
private Long id;
private Date gmtCreate;
private Date createTime;
private Long buyerId;
private Long age;
private String userNick;
private String userVerified;
}
定义mapstruct接口,在接口上打上@Mapper注解。
接口中有一个常量和一个方法,常量的值是接口的实现类,这个实现类是Mapstruct默认帮我们实现的,下文会讲到。定义了一个po2entity的转换方法,表示把入参UserPo对象,转换成UserEntity。注意@Mapper是Mapstruct的注解。
package mapstruct;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface IPersonMapper {
IPersonMapper INSTANCT = Mappers.getMapper(IPersonMapper.class);
UserEntity po2entity(UserPo userPo);
}
3、Mapstruct 性能优于 BeanUtils 的原因
Java程序执行的过程,是由编译器先把java文件编译成class字节码文件,然后由JVM去解释执行class文件。Mapstruct正是在java文件到class这一步帮我们实现了转换方法,即做了预处理,提前编译好文件,如果用过lombok的同学一定能理解其好处,通过查看class文件,可以看出IPersonMapper被打上org.mapstruct.Mapper注解后,编译器自动会帮我们生成一个实现类IPersonMapperImpl,并实现了po2entity这个方法。
BeanUtils转换的原理是使用的反射,反射的效率相对来说是低的,因为jvm优化在这种场景下有可能无效,所以在对性能要求很高或者经常被调用的程序中,尽量不要使用。我们平时在研发过程中,也会遵守这个原则,非必要,不反射。