每天早上七点三十,准时推送干货
日常编程中,我们会经常会碰到对象属性复制的场景,就比如下面这样一个常见的三层 MVC 架构。
![](https://i-blog.csdnimg.cn/blog_migrate/a4918f6d5e01d4d80d22392c7b3c62c6.png)
当我们在上面的架构下编程时,我们通常需要经历对象转化,比如业务请求流程经历三层机构后需要把 DTO
转为DO
然后在数据库中保存。
当需要从数据查询数据页面展示时,查询数据经过三层架构将会从 DO
转为 DTO
,最后再转为 VO
,然后在页面中展示。
当业务简单的时候,我们手写代码,通过 getter/setter
复制对象属性,十分简单。但是一旦业务变的复杂,对象属性变得很多,那么手写代码就会成为程序员的噩梦。
不但手写十分繁琐,非常耗时间,并且还可能容易出错。
阿粉之前就经历过一个项目,一个对象中大概有四五十个字段属性,那时候阿粉还刚入门,什么都不太懂,写了半天 getter/setter
复制对象属性。
话外音:一个对象属性这么多,显然是不太合理的,我们设计过程应该将其拆分。
直到后来,阿粉了解到了对象属性复制工具类,使用之后,发现是真香,再也不用手写代码。再后来,碰到越来越多工具类,虽然核心功能都是一样的,但是还是存在很多差异。新手看到可能会一脸懵逼,不知道如何选择。
所以阿粉今天这篇介绍一下市面上常用的工具类:
Apache BeanUtils
Spring BeanUtils
Cglib BeanCopier
Dozer
orika
MapStruct
工具类特性
在介绍这些工具类之前,我们来看下一个好用的属性复制工具类,需要有哪些特性:
基本属性复制,这个是基本功能
不同类型的属性赋值,比如基本类型与其包装类型等
不同字段名属性赋值,当然字段名应该尽量保持一致,但是实际业务中,由于不同开发人员,或者笔误拼错单词,这些原因都可能导致会字段名不一致的情况
浅拷贝/深拷贝,浅拷贝会引用同一对象,如果稍微不慎,同时改动对象,就会踩到意想不到的坑
下面我们开始介绍工具类。
画外音:公号内回复「20200822」获取源码
Apache BeanUtils
首先介绍是第一位应该是 Java 领域属性复制的最有名的工具类「Apache BeanUtils」,这个工具类想必很多人或多或少用过或则见过。
没用过也没关系,我们来展示这个类的用法,用法非常简单。
首先我们引入依赖,这里使用最新版本:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
假设我们项目中有如下类:
![](https://i-blog.csdnimg.cn/blog_migrate/4a7830f7bc0029b8412ef2de0080b714.png)
此时我们需要完成 DTO 对象转化到 DO 对象,我们只需要简单调用BeanUtils#copyProperties
方法就可以完成对象属性的复制。
StudentDTO studentDTO = new StudentDTO();
studentDTO.setName("阿粉");
studentDTO.setAge(18);
studentDTO.setNo("6666");
List<String> 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();
BeanUtils.copyProperties(studentDO, studentDTO);
不过,上面的代码如果你这么写,我们会碰到第一个问题,BeanUtils 默认不支持 String
转为 Date 类型。
![](https://i-blog.csdnimg.cn/blog_migrate/95839e7fb78b81f359254772ec7887de.png)
为了解决这个问题,我们需要自己构造一个 Converter
转换类,然后使用 ConvertUtils
注册,使用方法如下:
ConvertUtils.register(new Converter() {
@SneakyThrows
@Override
public <Date> Date convert(Class<Date> type, Object value) {
if (value == null) {
return null;
}
if (value instanceof String) {
String str = (String) value;
return (Date) DateUtils.parseDate(str, "yyyy-MM-dd");
}
return null;
}
}, Date.class);
此时,我们观察 studentDO
与 studentDTO
对象属性值:
![](https://i-blog.csdnimg.cn/blog_migrate/9e86f30f4d1cf0932742bc652bb6692e.png)
从上面的图我们可以得出BeanUtils一些结论:
普通字段名不一致的属性无法被复制
嵌套对象字段,将会与源对象使用同一对象,即使用浅拷贝
类型不一致的字段,将会进行默认类型转化。
虽然 BeanUtils 使用起来很方便,不过其底层源码为了追求完美,加了过多的包装,使用了很多反射,做了很多校验,所以导致性能较差,所以并阿里巴巴开发手册上强制规定避免使用 Apache BeanUtils。
![](https://i-blog.csdnimg.cn/blog_migrate/ecae0fe5b895dc3636c9be35007418a4.png)
Spring BeanUtils
Spring 属性复制工具类类名与 Apache
一样,基本用法也差不多。我先来看下 Spring BeanUtils 基本用法。
同样,我们先引入依赖,从名字我们可以看出,BeanUtils 位于 Spring-Beans
模块,这里我们依然使用最新模块。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
这里我们使用 DTO 与 DO 复用上面的例子,转换代码如下:
// 省略上面赋值代码,与上面一致
StudentDO studentDO = new StudentDO();
BeanUtils.copyProperties(studentDTO, studentDO);
从用法可以看到,Spring BeanUtils 与 A