利用cglib的BeanCopier用原型模式以及享元模式完成对象的拷贝

实际上对象拷贝的工具有很多种,比如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));
    }

利用cglib的BeanCopier用原型模式以及享元模式完成对象的拷贝

上一篇:JDK与CGLIB动态代理区别 && 使用示例


下一篇:模拟Java动态代理模式:CGLIB动态代理