Bean Copy

在日常开发中,经常会遇到需要Bean copy的情况,有好多copy的方法

克隆的分类

  • 浅克隆(shallow clone),浅克隆是指拷贝对象时仅仅copy对象本身和对象中的基本变量,而不拷贝对象包含的引用指向的对象。
  • 深克隆(deep clone),不仅copy对象本身,而且copy对象包含的引用指向的所有对象。

克隆方法

传统的setter方法

手工来setter对象的每个属性,这种方法性能非常高,但是写法不优雅、繁琐

Object.clone()

  • 实现java.lang.Cloneable接口
    Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆,能使用Object.clone()方法。如果没有实现 Cloneable的类对象调用clone()就会抛出CloneNotSupportedException。

  • 重写(Override)Object的clone()方法,并声明为public

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Student implements Cloneable {
    String name;
    int age;

    Student(String name,int age) {
    this.name=name;
    this.age=age;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    }

这只是浅克隆,如果需要深克隆的话则重写(Override)Object类的clone()方法,并且在方法内部调用持有对象的clone方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student implements Cloneable {
String name;
Info info;

Student(String name,Info info) {
this.name=name;
this.info=info;
}

@Override
public Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.info = (Info) student.info.clone();
return student;
}
}

这样就可以调用Object.clone()方法或调用org.apache.commons.lang3.ObjectUtils的clone()方法来克隆对象了

序列化来实现深克隆

自定义类需要实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象

使用第三方工具

常见的第三方工具大概有这几种:

Apache 的 BeanUtils
Apache 的 PropertyUtils
Spring 的 BeanUtils
Cglib 的 BeanCopier

Apache 的 BeanUtils 和 PropertyUtils:

Apache的BeanUtils和PropertyUtils性能非常差,一般都不使用。
这个俩个类都是浅克隆,Apache还提供了 SerializationUtils.clone(T),T对象需要实现Serializable接口,他属于深克隆。

Spring 的 BeanUtils

Spring的BeanUtils的BeanUtils.copyProperties()方法相比Apache来说性能要好很多。
并且相比Cglib 的 BeanCopier来说,它是静态方法,直接使用即可,非常方便。而BeanCopier不是静态方法,使用前需要使用BeanCopier.create()来初始化。

Cglib 的 BeanCopier

Cglib 的 BeanCopier相比来说性能是最高的,但是使用前需要通过BeanCopier.create()来初始化。

1
2
BeanCopier beanCopier = BeanCopier.create(srcClass, dstClass, false);
beanCopier.copy(srcObject, dstObject, null);

其实BeanCopier主要花费的时间是调用create()方法,真正执行copy Bean花费的时间较少,所以我们可以进行优化,不是每次执行创建新BeanCopier,将BeanCopier.create()创建的BeanCopier进行缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@UtilityClass
public class BeanCopierUtil {

private static Map<String, BeanCopier> cache = Maps.newConcurrentMap();

public static <S, D> void copy(S srcObject, D dstObject) {
BeanCopier beanCopier = getBeanCopier(srcObject.getClass(), dstObject.getClass());
beanCopier.copy(srcObject, dstObject, null);
}

private static BeanCopier getBeanCopier(Class<?> srcClass, Class<?> dstClass) {
String key = Joiner.on('\u0003').join(srcClass.getName(), dstClass.getName());
return cache.computeIfAbsent(key, k -> BeanCopier.create(srcClass, dstClass, false));
}
}

加入缓存后和手工set的性能接近


建议不要使用Apache的工具,如果是偶尔使用为了方便可以使用Spring的BeanUtils,如果是频繁使用时还是推荐使用Cglib 的 BeanCopier

分享到: