对象copy源码分析与性能测试

1.前言

项目中经常会遇到对象copy的场景,如何安全高效便捷copy对象,自然成为提高开发效率与系统性能的关注点。
通常较为流行的对象copy方法有以下4个:
- net.sf.cglib.beans.BeanCopier.copy(Object from, Object to, Converter converter)
- org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)
- org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)
- org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig)
它们的实现原理及特性如下表所示:

FeaturesCglib-BeanCopier.copySpring-BeanUtils.copyPropertiesApache-BeanUtils.copyPropertiesApache-PropertyUtils.copyProperties
实现原理cglib动态代理反射反射反射
复制哪些属性复制源对象和目标对象中相同名称的属性复制源对象和目标对象中相同名称的属性复制源对象和目标对象中相同名称的属性复制源对象和目标对象中相同名称与类型的属性
是否有类型转换有,需要自定义实现Converter接口的转换器没有有,API默认注册了常用的转换器,也可以通过ConvertUtils.register方法添加自定义转换器没有,这也是性能比Apache-BeanUtils快的主要原因
类型不匹配处理方式useConverter=true时类型不匹配抛异常,不复制任何属性;useConverter=false时,只复制类型匹配属性类型不匹配属性不复制;只复制类型相同或源属性类型派生自目标属性类型的属性有类型转换器则进行类型转换,没有或转换错误时抛异常不再复制属性抛异常,不复制任何属性

2.Spring BeanUtils.copyProperties源码分析

BeanUtils有3个对象copy的静态方法,源码如下:

public static void copyProperties(Object source, Object target) throws BeansException {
    copyProperties(source, target, null, (String[]) null);
}

public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException {
    copyProperties(source, target, editable, (String[]) null);
}

public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
    copyProperties(source, target, null, ignoreProperties);
}

/**
 * 复制对象
 * @param source 源对象
 * @param target 目标对象
 * @param editable 要copy的属性所属的对象类型,可以是taget对象的类型或其父类/接口类型
 * @param ignoreProperties 不需要copy的属性名称
 */
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
            throws BeansException {

    Assert.notNull(source, "Source must not be null");
    Assert.notNull(target, "Target must not be null");

    /*
     * 获取要copy的属性所属的对象类型。
     * 如果target类型与editable相同,则copy target类型的所有属性;
     * 如果target类型是editable的子类型,则copy editable类型的所有属性;
     */
    Class<?> actualEditable = target.getClass();
    if (editable != null) {
        if (!editable.isInstance(target)) {
            throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                "] not assignable to Editable class [" + editable.getName() + "]");
        }
        actualEditable = editable;
    }
    // 要copy的属性描述对象数组
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

    for (PropertyDescriptor targetPd : targetPds) {
        Method writeMethod = targetPd.getWriteMethod();
        // 忽略掉不需要copy的属性
        if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
            PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
            if (sourcePd != null) {
                Method readMethod = sourcePd.getReadMethod();
                // 如果源对象与目标对象属性类型不一致,则不copy属性
                if (readMethod != null &&
                    ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                    try {
                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                            readMethod.setAccessible(true);
                        }
                        // 反射获取源对象属性值
                        Object value = readMethod.invoke(source);
                        if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                            writeMethod.setAccessible(true);
                        }
                        // 反射设置目标对象属性值
                        writeMethod.invoke(target, value);
                    }
                    catch (Throwable ex) {
                        throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                    }
                }
            }
        }
    }
}

3.性能测试

下面是对四个对象copy方法进行性能测试的代码:

import java.util.Date;
import org.junit.Test;
import net.sf.cglib.beans.BeanCopier;
import net.sf.cglib.core.Converter;
/**
 * 测试代码
 */
public class BeanCopierTest {
    // copy对象数量
    final int beanCount = 500000;

    @Test
    public void testCglibBeanCopier() {
        long start = System.currentTimeMillis();
        BeanCopier beanCopier = BeanCopier.create(UserEntity.class, UserResultDto.class, true);
        for (int i = 1; i <= beanCount; i++) {
            UserEntity entity = createUser(i);
            UserResultDto dto = new UserResultDto();
            beanCopier.copy(entity, dto, new Converter() {
                @Override
                public Object convert(Object value, @SuppressWarnings("rawtypes") Class target, Object context) {
                    // 自定义转换逻辑
                    return value;
                }
            });
        }
        System.out.println("CglibBeanCopier,耗时(ms):" + (System.currentTimeMillis() - start));
    }

    @Test
    public void testApacheBeanUtilsCopier() throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 1; i <= beanCount; i++) {
            UserEntity entity = createUser(i);
            UserResultDto dto = new UserResultDto();
            org.apache.commons.beanutils.BeanUtils.copyProperties(dto, entity);
        }
        System.out.println("ApacheBeanUtilsCopier,耗时(ms):" + (System.currentTimeMillis() - start));
    }

    @Test
    public void testApachePropertyUtilsCopier() throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 1; i <= beanCount; i++) {
            UserEntity entity = createUser(i);
            UserResultDto dto = new UserResultDto();
            org.apache.commons.beanutils.PropertyUtils.copyProperties(dto, entity);
        }
        System.out.println("ApachePropertyUtilsCopier,耗时(ms):" + (System.currentTimeMillis() - start));
    }

    @Test
    public void testSpringBeanUtilsCopier() throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 1; i <= beanCount; i++) {
            UserEntity entity = createUser(i);
            UserResultDto dto = new UserResultDto();
            org.springframework.beans.BeanUtils.copyProperties(entity, dto);
        }
        System.out.println("SpringBeanUtilsCopier,耗时(ms):" + (System.currentTimeMillis() - start));
    }

    private UserEntity createUser(Integer id) {
        UserEntity entity = new UserEntity();
        entity.setId(id);
        entity.setUserName("un" + id);
        entity.setRealName("rn" + id);
        entity.setPassword("pwd" + id);
        entity.setEmail("xyz" + id + "@126.com");
        entity.setOptId(9999);
        entity.setAddTime(new Date());
        entity.setUpdateTime(new Date());
        return entity;
    }

}

源对象代码

import java.util.Date;

public class UserEntity {

    private Integer id;
    private String userName;
    private String realName;
    private String password;
    private String email;
    private Integer optId;
    private Date addTime;
    private Date updateTime;

    // setter and getter methods...

}

目标对象代码

import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;

public class UserResultDto {

    private Integer id;
    private String userName;
    private String realName;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date addTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

    // setter and getter methods...

    @Override
    public String toString() {
        return "UserResultDto [id=" + id + ", userName=" + userName + ", realName=" + realName + ", addTime=" + addTime
                + ", updateTime=" + updateTime + "]";
    }

}

测试结果

复制对象数量(beanCount)Cglib-BeanCopier.copy耗时(ms)Spring-BeanUtils.copyProperties耗时(ms)Apache-BeanUtils.copyProperties耗时(ms)Apache-PropertyUtils.copyProperties耗时(ms)
106390485
206092489
50631026418
10057938232
200619412341
5005710319273
10005911327385
200060132382137
500061164603289
1000062172882533
200006018214331011
500006819831242523
1000007423457344940
200000952841110510195
5000001364822734524385


总结
Cglib-BeanCopier.copy:
性能稳定高效,大量复制同一类型对象时首选,也可以通过缓存beanCopier对象来提升性能。
Spring-BeanUtils.copyProperties:
性能好,适用场景:只需要复制源对象部分属性且不需要类型转换。
Apache-BeanUtils.copyProperties:
提供了很多类型转换器,在复制过程中若类型不相同,可以自动进行类型转换,正因为如此其性能比较低。
Apache-PropertyUtils.copyProperties:
只进行简单的对象复制,不做类型转换,这也是其性能比Apache-BeanUtils.copyProperties好的主要原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值