采取CGlib达成Bean备份(BeanCopier)版权声明

原创
小哥 3年前 (2022-10-27) 阅读数 23 #大杂烩

目录

在做生意时,我们有时为了隔离变化,会DAO向外查询Entity,并由外部提供DTO与世隔绝。近似值90%当时,他们的结构很相似,但我们不喜欢写太多冗长的内容b.setF1(a.getF1())这样的代码,所以我们需要BeanCopier来帮助我们。选择Cglib的BeanCopier进行Bean复制的原因是它的性能更好Spring的BeanUtils,Apache的BeanUtils和PropertyUtils这要好得多,特别是在数据量相对较大的情况下。

回到顶部

BeanCopier基本用法

public class User { private int age; private String name;

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}

public class UserDto { private int age; private String name;

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}

public class UserWithDiffType { private Integer age; private String name;

public Integer getAge() {
    return age;
}

public void setAge(Integer age) {
    this.age = age;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}

  1. 属性名称和类型相同。:

@Test public void normalCopyTest() { // create(Class source, Class target, boolean useConverter) final BeanCopier beanCopier = BeanCopier.create(User.class, UserDto.class, false**); User user = new User(); user.setAge(10); user.setName("zhangsan"); UserDto userDto = new UserDto(); beanCopier.copy(user, userDto, null**); Assert.assertEquals(10, userDto.getAge()); Assert.assertEquals("zhangsan", userDto.getName()); }

结论: 相同类型、相同房产名称的房产复印件OK

  1. 属性名称相同,类型不同:

@Test public void normalCopyTest() { // create(Class source, Class target, boolean useConverter) final BeanCopier beanCopier = BeanCopier.create(User.class, UserWithDiffType.class, false**); User user = new User(); user.setAge(10); user.setName("zhangsan"); UserWithDiffType userDto = new UserWithDiffType(); beanCopier.copy(user, userDto, null**); Assert.assertEquals(null, userDto.getAge()); Assert.assertEquals("zhangsan", userDto.getName()); }

结论: 不复制特性名称相同但类型不同的特性。
注意:即使源类型是原始类型(int, short和char等),目标类型是它的包装类型。(Integer, Short和Character等),或者反之亦然:两者都是 不会被复制。

总结:
BeanCopier只拷贝 具有相同名称和类型的属性

回到顶部

自定义转换器

当源类和目标类的属性类型不同时,不能复制该属性,此时我们可以实现Converter接口自定义转换器
源类和目标类:

public class Account { private int id; private Date createTime; private BigDecimal balance;

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public Date getCreateTime() {
    return createTime;
}

public void setCreateTime(Date createTime) {
    this.createTime = createTime;
}

public BigDecimal getBalance() {
    return balance;
}

public void setBalance(BigDecimal balance) {
    this.balance = balance;
}

}

public class AccountDto { private int id; private String createTime; private String balance;

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getCreateTime() {
    return createTime;
}

public void setCreateTime(String createTime) {
    this.createTime = createTime;
}

public String getBalance() {
    return balance;
}

public void setBalance(String balance) {
    this.balance = balance;
}

}

  1. 不使用Converter

@Test public void noConverterTest() { Account po = new Account(); po.setId(1); po.setCreateTime(new Date()); po.setBalance(BigDecimal.valueOf(4000L)); BeanCopier copier = BeanCopier.create(Account.class, AccountDto.class, false); AccountDto dto = new AccountDto(); copier.copy(po, dto, null); // 不同类型,未复制 Assert.assertNull(dto.getBalance()); // 不同类型,未复制 Assert.assertNull(dto.getCreateTime()); }

  1. 使用Converter
    根据目标对象的特性,如果源对象具有相同名称的特性,则调整一次。convert方法:

public class TestCase {

@Test
public void noConverterTest() {
    Account po = new Account();
    po.setId(1);
    po.setCreateTime(new Date());
    po.setBalance(BigDecimal.valueOf(4000L));
    BeanCopier copier = BeanCopier.create(Account.class, AccountDto.class, true);
    AccountDto dto = new AccountDto();
    AccountConverter converter = new AccountConverter();
    copier.copy(po, dto, converter);
    // 不同类型,未复制
    Assert.assertEquals("4000", dto.getBalance());
    // 不同类型,未复制
    Assert.assertEquals("2018-12-13", dto.getCreateTime());
}

}

class AccountConverter implements Converter {

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

@SuppressWarnings("rawtypes")
@Override
public Object convert(Object source, Class target, Object context) {
    if (source instanceof Integer) {
        return (Integer) source;
    } else if (source instanceof Date) {
        Date date = (Date) source;
        return sdf.format(date);
    } else if (source instanceof BigDecimal) {
        BigDecimal bd = (BigDecimal) source;
        return bd.toPlainString();
    }
    return null;
}

}

注:一旦使用Converter,BeanCopier只使用Converter定义的规则复制属性,因此在中。convert方法中应考虑所有属性。

回到顶部

封装BeanCopier

@Test public void costTest() { List list1 = new ArrayList<>(100); for (int i = 0; i < 100; i++) { User po = new User(); po.setId(1); po.setCreateTime(new Date()); po.setBalance(BigDecimal.valueOf(4000L)); list1.add(po); } BeanCopier copier = BeanCopier.create(User.class, UserDto.class, false); long start = System.currentTimeMillis(); List list2 = new ArrayList<>(100); for (User user : list1) { UserDto dto = new UserDto(); //BeanUtils.beanCopy(user, dto); copier.copy(user, dto, null); list2.add(dto); } System.out.printf("took time: %d(ms)%n",System.currentTimeMillis() - start); }

经过测试,BeanCopier性能是BeanUtils10倍左右。

BeanCopier拷贝速度很快,创建过程中会出现性能瓶颈BeanCopier在实例的过程中。 所以,把创造的BeanCopier实例放入缓存,下次可直接获取以提升性能:

依赖:

cglib cglib 3.2.10 com.esotericsoftware reflectasm 1.11.7

封装工具

public class WrapperBeanCopier {

private WrapperBeanCopier() {
    //do nothing
}

private static final Map BEAN\_COPIER\_CACHE = new ConcurrentHashMap<>();

private static final Map CONSTRUCTOR\_ACCESS\_CACHE = new ConcurrentHashMap<>();

public static void copyProperties(Object source, Object target) {
    BeanCopier copier = getBeanCopier(source.getClass(), target.getClass());
    copier.copy(source, target, null);
}

private static BeanCopier getBeanCopier(Class sourceClass, Class targetClass) {
    String beanKey = generateKey(sourceClass, targetClass);
    BeanCopier copier = null;
    if (!BEAN\_COPIER\_CACHE.containsKey(beanKey)) {
        copier = BeanCopier.create(sourceClass, targetClass, false);
        BEAN\_COPIER\_CACHE.put(beanKey, copier);
    } else {
        copier = BEAN\_COPIER\_CACHE.get(beanKey);
    }
    return copier;
}

/**
 * 这两个类的完全限定名拼接在一起以形成Key
 *
 * @param sourceClass
 * @param targetClass
 * @return
 */
private static String generateKey(Class sourceClass, Class targetClass) {
    return sourceClass.getName() + targetClass.getName();
}

public static  T copyProperties(Object source, Class targetClass) {
    T t = null;
    try {
        t = targetClass.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
        throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
    }
    copyProperties(source, t);
    return t;
}

public static  List copyPropertiesOfList(List sourceList, Class targetClass) {
    if (sourceList == null || sourceList.isEmpty()) {
        return Collections.emptyList();
    }
    ConstructorAccess constructorAccess = getConstructorAccess(targetClass);
    List resultList = new ArrayList<>(sourceList.size());
    for (Object o : sourceList) {
        T t = null;
        try {
            t = constructorAccess.newInstance();
            copyProperties(o, t);
            resultList.add(t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    return resultList;
}

private static  ConstructorAccess getConstructorAccess(Class targetClass) {
    ConstructorAccess constructorAccess = CONSTRUCTOR\_ACCESS\_CACHE.get(targetClass.getName());
    if (constructorAccess != null) {
        return constructorAccess;
    }
    try {
        constructorAccess = ConstructorAccess.get(targetClass);
        constructorAccess.newInstance();
        CONSTRUCTOR\_ACCESS\_CACHE.put(targetClass.toString(), constructorAccess);
    } catch (Exception e) {
        throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
    }
    return constructorAccess;
}

}

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除