MapStruct ≈ Cglib BeanCopier > Spring BeanUtils > Apache BeanUtils
定义bean:
测试:
String不能转换为Date类型,所以需要自定义转换器并注册。
@Test
public void test() throws InvocationTargetException, IllegalAccessException {
StudentDTO studentDTO = new StudentDTO();
studentDTO.setName("小明");
studentDTO.setAge(18);
studentDTO.setNo("6666");
List subjects = new ArrayList<>();
subjects.add("math");
subjects.add("english");
studentDTO.setSubjects(subjects);
studentDTO.setCourse(new Course("CS-1"));
studentDTO.setCreateDate("2020-08-08");
StudentDO studentDO = new StudentDO();
ConvertUtils.register(new Converter() {
@SneakyThrows
@Override
public Date convert(Class type, Object value) {
if (value == null) {
return null;
}
if (value instanceof String) {
return (Date) DateUtils.parseDate((String)value, "yyyy-MM-dd");
}
return null;
}
}, Date.class);
BeanUtils.copyProperties(studentDO, studentDTO);
System.out.println(studentDO);
}
结果:
结论:
测试:要注意spring beanUtils的参数(源、目标)和apache beanUtils是相反的。
// 赋值代码同上
BeanUtils.copyProperties(studentDTO, studentDO);
结论:
测试:bean定义同上
// 赋值代码同上
BeanCopier copier = BeanCopier.create(StudentDTO.class, StudentDO.class, false);
copier.copy(studentDTO, studentDO, null);
结果:
结论:
当类型不一致时,可以使用自定义转换器:
BeanCopier copier = BeanCopier.create(StudentDTO.class, StudentDO.class, true);
copier.copy(studentDTO, studentDO, new Converter() {
@SneakyThrows
@Override
public Object convert(Object o, Class aClass, Object o1) {
if (o instanceof String) {
return DateUtils.parseDate((String)o, "yyyy-MM-dd");
} else if (o instanceof Integer) {
return String.valueOf(o);
}
return o;
}
});
但是有个问题是如果同一种类型,但是目标字段的类型不同,则处理不了。比如示例中的createDate、no、name都是String类型,createDate需要转换为Date,后两者不需要类型转换。
Cglib BeanCopier 的原理与上面两个 Beanutils 原理不太一样,其主要使用 字节码技术动态生成一个代理类,代理类实现get 和 set方法。生成代理类过程存在一定开销,但是一旦生成,我们可以缓存起来重复使用,所有 Cglib 性能相比以上两种 Beanutils 性能比较好。
依赖:
org.mapstruct
mapstruct
1.4.1.Final
org.mapstruct
mapstruct-processor
1.4.1.Final
bean定义同上
测试:
@Mapper(componentModel = "spring")
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
StudentDO convert(StudentDTO dto);
}
// componentModel = "spring": 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的@Autowired方式进行注入。
// Mappers.getMapper(Class): 获取自动生成的实例对象,便于在没有启动spring容器的时候使用。
createDate字段从String转换Date异常,需要使用注解@Mapping指定转换方式
如果表达式中的语句会抛异常,需要做下封装(比如DateUtils.parseDate封装到DateUtilsParse),明确捕获异常才行,否则编译失败(java: 未报告的异常错误java.text.ParseException; 必须对其进行捕获或声明以便抛出)
错误示范:
@Mapping(target = "createDate", expression = "java(org.apache.commons.lang3.time.DateUtils.parseDate(dto.getCreateDate(), \"yyyy-MM-dd\"))")
正确示范:
@Mapper(componentModel = "spring")
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
@Mapping(target = "createDate", expression = "java(DateUtilsParse(dto.getCreateDate()))")
StudentDO convert(StudentDTO dto);
default Date DateUtilsParse(String dateStr) {
try {
return DateUtils.parseDate(dateStr, "yyyy-MM-dd");
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
结果:
target目录下查看编译结果,自动生成实现类:
@Component
public class StudentMapperImpl implements StudentMapper {
@Override
public StudentDO convert(StudentDTO dto) {
if ( dto == null ) {
return null;
}
StudentDO studentDO = new StudentDO();
studentDO.setNumber( dto.getNo() );
studentDO.setName( dto.getName() );
if ( dto.getAge() != null ) {
studentDO.setAge( String.valueOf( dto.getAge() ) ); // 可见是浅拷贝
}
List list = dto.getSubjects();
if ( list != null ) {
studentDO.setSubjects( new ArrayList( list ) ); // 可见是深拷贝
}
studentDO.setCourse( dto.getCourse() );
studentDO.setCreateDate( DateUtilsParse(dto.getCreateDate()) );
return studentDO;
}
}
bean定义中有枚举字段的情况:
public enum GenderEnum {
BOY("boy", "男孩"),
GIRL("girl", "女孩");
GenderEnum(String code, String desc) {
}
}
源字段类型:String
目标字段类型:GenderEnum
编译生成的实现:
if ( dto.getGender() != null ) {
studentDO.setGender( Enum.valueOf( GenderEnum.class, dto.getGender() ) );
}
源字段类型:GenderEnum
目标字段类型:String
编译生成的实现:
if ( dto.getGender() != null ) {
studentDO.setGender( dto.getGender().name() );
}
结论:
Apache BeanUtiles底层源码为了追求完美,加了过多的包装,使用了很多反射,做了很多校验,导致性能较差,所以阿里巴巴开发手册上强制规定避免使用 Apache BeanUtil