使用CGlib实现Bean拷贝(BeanCopier)「建议收藏」
在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要BeanCopier来帮助我们。BeanCopier其实已经有很多开源版本,例如DozerMapper、Apache BeanUtils、Spring、Jodd BeanUtils甚至是Cglib都提供了这样的功能。下面介绍Cglib的BeanCopier的使用。
1、通过拷贝bean对象来测试BeanCopier的基本使用和特性 Java代码 收藏代码 public class OrderEntity { private int id; private String name; // Getters and setters are omitted }
Java代码 收藏代码 public class OrderDto { private int id; private String name; // Getters and setters are omitted }
Java代码 收藏代码 public class PropWithDiffType { private Integer id; private String name; // Getters and setters are omitted }
Java代码 收藏代码 public class LackOfSetter { private int id; private String name;
public LackOfSetter() {
}
public LackOfSetter(int id, String name) {
this.id = id;
this.name = name;
}
// Getters and setters are omitted
// public void setName(String name) {
// this.name = name;
// }
}
- 属性名称、类型都相同: Java代码 收藏代码 @Test public void normalCopyTest() { OrderEntity entity = new OrderEntity(); entity.setId(1); entity.setName(“orderName”); final BeanCopier copier = BeanCopier.create(OrderEntity.class, OrderDto.class, false); OrderDto dto = new OrderDto(); copier.copy(entity, dto, null); Assert.assertEquals(1, dto.getId()); Assert.assertEquals(“orderName”, dto.getName()); }
结论:拷贝OK。 2. 属性名称相同、类型不同:
Java代码 收藏代码 @Test public void sameNameDifferentTypeCopyTest() { OrderEntity entity = new OrderEntity(); entity.setId(1); entity.setName(“orderName”); final BeanCopier copier = BeanCopier.create(OrderEntity.class, PropWithDiffType.class, false); PropWithDiffType dto = new PropWithDiffType(); copier.copy(entity, dto, null); Assert.assertEquals(null, dto.getId()); // OrderEntity的id为int类型,而PropWithDiffType的id为Integer类型,不拷贝 Assert.assertEquals(“orderName”, dto.getName()); }
结论:名称相同而类型不同的属性不会被拷贝。
注意:即使源类型是原始类型(int, short和char等),目标类型是其包装类型(Integer, Short和Character等),或反之:都不会被拷贝。
- 源类和目标类有相同的属性(两者的getter都存在),但目标类的setter不存在 Java代码 收藏代码 @Test public void targetLackOfSetterCopyTest() { OrderEntity entity = new OrderEntity(); entity.setId(1); entity.setName(“orderName”); final BeanCopier copier = BeanCopier.create(OrderEntity.class, LackOfSetter.class, false); // 抛NullPointerException LackOfSetter dto = new LackOfSetter(); copier.copy(entity, dto, null); }
结论:创建BeanCopier的时候抛异常。
导致异常的原因是BeanCopier类的第128~133行 Java代码 收藏代码 for (int i = 0; i < setters.length; i++) { // 遍历目标类的属性描述集 PropertyDescriptor setter = setters[i]; PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName()); // 从源类获取和目标类属性名称相同的属性描述 if (getter != null) { MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod()); // 获取源类属性的getter方法 MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod()); // 获取目标类属性的setter方法。LackOfSetter类name属性的setter方法没有,所以报错
- 源类或目标类的setter比getter少 Java代码 收藏代码 @Test public void sourceLackOfSetterCopyTest() { LackOfSetter source = new LackOfSetter(1, “throne”); final BeanCopier copier = BeanCopier.create(LackOfSetter.class, OrderDto.class, false); OrderDto dto = new OrderDto(); copier.copy(source, dto, null); Assert.assertEquals(1, dto.getId()); Assert.assertEquals(“throne”, dto.getName()); }
结论:拷贝OK。此时的setter多余,但不会报错。
总结:
- BeanCopier只拷贝名称和类型都相同的属性。
- 当目标类的setter数目比getter少时,创建BeanCopier会失败而导致拷贝不成功。 二、自定义转换器 当源和目标类的属性类型不同时,不能拷贝该属性,此时我们可以通过实现Converter接口来自定义转换器 源类和目标类:
Java代码 收藏代码 public class AccountEntity { private int id; private Timestamp createTime; private BigDecimal balance; // Getters and setters are omitted }
Java代码 收藏代码 public class AccountDto { private int id; private String name; private String createTime; private String balance; // Getters and setters are omitted }
- 不使用Converter Java代码 收藏代码 public class BeanCopierConverterTest { @Test public void noConverterTest() { AccountEntity po = new AccountEntity(); po.setId(1); po.setCreateTime(new Timestamp(10043143243L)); po.setBalance(BigDecimal.valueOf(4000L)); BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.class, false); AccountDto dto = new AccountDto(); copier.copy(po, dto, null); Assert.assertNull(dto.getCreateTime()); // 类型不同,未拷贝 Assert.assertNull(dto.getBalance()); // 类型不同,未拷贝 } }
- 使用Converter 基于目标对象的属性出发,如果源对象有相同名称的属性,则调一次convert方法: Java代码 收藏代码 package net.sf.cglib.core;
public interface Converter { // value 源对象属性,target 目标对象属性类,context 目标对象setter方法名 Object convert(Object value, Class target, Object context); } Java代码 收藏代码 @Test public void converterTest() { AccountEntity po = new AccountEntity(); po.setId(1); po.setCreateTime(Timestamp.valueOf(“2014-04-12 16:16:15”)); po.setBalance(BigDecimal.valueOf(4000L)); BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.class, true); AccountConverter converter = new AccountConverter(); AccountDto dto = new AccountDto(); copier.copy(po, dto, converter); Assert.assertEquals(“2014-04-12 16:16:15”, dto.getCreateTime()); Assert.assertEquals(“4000”, dto.getBalance()); }
static class AccountConverter implements Converter {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@SuppressWarnings("rawtypes")
@Override
public Object convert(Object value, Class target, Object context) {
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Timestamp) {
Timestamp date = (Timestamp) value;
return sdf.format(date);
} else if (value instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) value;
return bd.toPlainString();
}
return null;
}
} 注:一旦使用Converter,BeanCopier只使用Converter定义的规则去拷贝属性,所以在convert方法中要考虑所有的属性。 三、缓存BeanCopier提升性能 BeanCopier拷贝速度快,性能瓶颈出现在创建BeanCopier实例的过程中。 所以,把创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能: Java代码 复制代码 收藏代码 public class CachedBeanCopier {
static final Map<String, BeanCopier> BEAN_COPIERS = new HashMap<String, BeanCopier>();
public static void copy(Object srcObj, Object destObj) {
String key = genKey(srcObj.getClass(), destObj.getClass());
BeanCopier copier = null;
if (!BEAN_COPIERS.containsKey(key)) {
copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);
BEAN_COPIERS.put(key, copier);
} else {
copier = BEAN_COPIERS.get(key);
}
copier.copy(srcObj, destObj, null);
}
private static String genKey(Class<?> srcClazz, Class<?> destClazz) {
return srcClazz.getName() + destClazz.getName();
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
相关文章
- 使用Golang实现PHP的Addslashes和Stripslashes「建议收藏」
- mt4 python_一个使用Python自动化交易外汇MT4脚本实现「建议收藏」
- 基于Zookeeper实现分布式锁,如何解决羊群问题?
- 不背锅运维:Go实现aes加密,并带你手撸一个命令行应用程序
- CentOS7 重新分配分区大小的实现方法
- 实现mysql数据库备份的方法(c备份mysql数据库)
- 命令使用1命令快速实现Linux中的任务(linux中1)
- javascript采用Broadway实现安卓视频自动播放的方法(这种坑比较多 不建议使用)详解编程语言
- 使用SQL Server实现行转列(sqlserver行转列)
- Linux创建备份:简单步骤轻松实现(linux创建备份文件)
- 键清理使用Java实现Redis过期键清理(redisjava过期)
- 使用 Linux 和 Qt 实现高效应用程序开发(linuxqt使用)
- 使用Linux正则表达式实现快速删除(linux+正则删除)
- MSSQL中使用TOP指令——轻松实现数据筛选与排序(mssql top)
- 让MySQL和Oracle的数据同步一种实现方法(mys同步oracle)
- 如何使用MySQL实现数据库表的分割(.mysql怎么分表)
- 使用YML配置Redis节点实现分布式集群(yml redis 节点)
- 掌控Redis列表实现修改排序的做法(修改redis列表排序)
- 使用Redis实现单点登录安全保障(使用redis单点登录)
- 使用Redis配置实现高效存储(redis 配置 存储)
- 使用Redis实现聚合运算的充分利用(redis聚合运算)
- C/C++实现矩阵的转置(示例代码)
- 使用JQuery库提供的扩展功能实现自定义方法