实际上对象拷贝的工具有很多种,比如apache BeanUtils、apache PropertyUtils、spring BeanUtils。在一些业务代码中现在经常看到的都是spring BeanUtils来进行对象拷贝。大部分情况下来说已经足够了,但如果居于性能考虑,以上几种工具都是利用反射的原理来完成的,性能相比cglib beanCopier利用动态代理实现稍差一筹,这里不去对比几种工具的性能,只展示BeanCopier如何进行使用。
一、非cglib下的对象拷贝方式
1、原型模式就是从一个已存在的对象创建出另一个对象
在实际业务开发中我们经常需要完成对象从DO到DTO或者VO的拷贝,大部分情况下,DTO/VO的属性都会与DO中的属性保持一致,如果每次都使用set方法去设置值就太麻烦了。需要通过一个通用的方法完成对象拷贝,如可以覆盖Object类的clone方法完成克隆操作
package com.zcj.javabase.designpattern.prototype;
/**
* 学生DO
* @author zcj
* @date 2021/3/24 06:16
*/
public class StudentDO {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2、覆盖父类clone的局限性
使用覆盖父类的clone返回的是一个Object对象,使用时可强转一个新的StudentDO对象,但我们常常需要做的是从StudentDO拷贝一个StudentVO出来。此时就需要我们自己实现一个新的对象拷贝的方法,可传入一个类型参数,返回一个对应类型的对象出去。
public <T> T clone(Class<T> clazz) {
T clazzInstance = null;
try {
clazzInstance = clazz.newInstance();
Method setId = clazz.getMethod("setId", Long.class);
setId.invoke(clazzInstance, this.id);
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(clazzInstance, this.name);
} catch (Exception e) {
e.printStackTrace();
}
return clazzInstance;
}
这样的做法是不通用,并且属性非常多的时候,就会特别的麻烦,可以使用BeanUtils.copyProperties替换掉原来的一大堆getMethod和invoke,如:
public <T> T clone(Class<T> clazz) {
T clazzInstance = null;
try {
clazzInstance = clazz.newInstance();
BeanUtils.copyProperties(this, clazzInstance);
} catch (Exception e) {
e.printStackTrace();
}
return clazzInstance;
}
二、cglib的BeanCopier的的方式实现对象拷贝
1、引入对象的依赖(正常项目中spring会有cglib的包,可直接使用spring的BeanCopier类,而不用单独的引入以下依赖)
<dependency>
<groupId>asm</groupId>
<artifactId>asm-all</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
2、增加工具类(工具类中实现了享元模式)
享元模式解决的是当我们需要多次使用某个对象时,只需要创建一次,然后缓存起来,后续再使用的时候直接获取。而不用每次使用都进行创建,可通过Map进行缓存起来。这里的工具类就是使用Map把生成的BeanCopier缓存起来
package com.zcj.javabase.designpattern.flyweight;
import org.springframework.cglib.beans.BeanCopier;
import java.util.HashMap;
import java.util.Map;
/**
* 对象拷贝工具类
* @author zcj
* @since 2021/2/24
*/
public class BeanCopierUtils {
/**
* 缓存beanCopier对象
*/
private static final Map<String, BeanCopier> BEAN_COPIER_MAP = new HashMap<>();
/**
* 属性拷贝
* @param source 源对象
* @param target 目标对象
*/
public static void copyProperties(Object source, Object target) {
String beanKey = generateKey(source.getClass(), target.getClass());
BeanCopier copier = BEAN_COPIER_MAP.get(beanKey);
if (copier == null) {
synchronized (BeanCopierUtils.class) {
if (!BEAN_COPIER_MAP.containsKey(beanKey)) {
copier = BeanCopier.create(source.getClass(), target.getClass(), false);
BEAN_COPIER_MAP.put(beanKey, copier);
} else {
copier = BEAN_COPIER_MAP.get(beanKey);
}
}
}
copier.copy(source, target, null);
}
private static String generateKey(Class<?> sourceClass, Class<?> targetClass) {
return sourceClass.toString() + targetClass.toString();
}
}
3、修改原来的clone方法
public <T> T clone(Class<T> clazz) {
T clazzInstance = null;
try {
clazzInstance = clazz.newInstance();
BeanCopierUtils.copyProperties(this, clazzInstance);
} catch (Exception e) {
e.printStackTrace();
}
return clazzInstance;
}
4、测试
package com.zcj.javabase.designpattern.prototype;
/**
* 学生VO
* @author zcj
* @date 2021/3/24 06:16
*/
public class StudentVO {
private Long id;
private String name;
private String address;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
public static void main(String[] args) {
StudentDO studentDO = new StudentDO();
studentDO.setId(100L);
studentDO.setName("zcj");
StudentVO clone = studentDO.clone(StudentVO.class);
System.out.println(JSONObject.toJSONString(clone));
}