使用CGlib实现Bean拷贝(BeanCopier)

在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要BeanCopier来帮助我们。BeanCopier其实已经有很多开源版本,例如DozerMapperApache BeanUtilsSpringJodd BeanUtils甚至是Cglib都提供了这样的功能。下面介绍Cglib的BeanCopier的使用。


1、通过拷贝bean对象来测试BeanCopier的基本使用和特性

Java代码   收藏代码
  1. public class OrderEntity {  
  2.     private int id;  
  3.     private String name;  
  4.     // Getters and setters are omitted  
  5. }  

Java代码   收藏代码
  1. public class OrderDto {  
  2.     private int id;  
  3.     private String name;  
  4.     // Getters and setters are omitted  
  5. }  

Java代码   收藏代码
  1. public class PropWithDiffType {  
  2.     private Integer id;  
  3.     private String name;  
  4.     // Getters and setters are omitted  
  5. }  

Java代码   收藏代码
  1. public class LackOfSetter {  
  2.     private int id;  
  3.     private String name;  
  4.   
  5.     public LackOfSetter() {  
  6.     }  
  7.   
  8.     public LackOfSetter(int id, String name) {  
  9.         this.id = id;  
  10.         this.name = name;  
  11.     }  
  12.     // Getters and setters are omitted  
  13.     // public void setName(String name) {  
  14.     //  this.name = name;  
  15.     // }  
  16. }  

1. 属性名称、类型都相同: 
Java代码   收藏代码
  1. @Test  
  2. public void normalCopyTest() {  
  3.     OrderEntity entity = new OrderEntity();  
  4.     entity.setId(1);  
  5.     entity.setName("orderName");  
  6.     final BeanCopier copier = BeanCopier.create(OrderEntity.class, OrderDto.classfalse);  
  7.     OrderDto dto = new OrderDto();  
  8.     copier.copy(entity, dto, null);  
  9.     Assert.assertEquals(1, dto.getId());  
  10.     Assert.assertEquals("orderName", dto.getName());  
  11. }  

结论:拷贝OK。 

2. 属性名称相同、类型不同: 

Java代码   收藏代码
  1. @Test  
  2. public void sameNameDifferentTypeCopyTest() {  
  3.     OrderEntity entity = new OrderEntity();  
  4.     entity.setId(1);  
  5.     entity.setName("orderName");  
  6.     final BeanCopier copier = BeanCopier.create(OrderEntity.class, PropWithDiffType.classfalse);  
  7.     PropWithDiffType dto = new PropWithDiffType();  
  8.     copier.copy(entity, dto, null);  
  9.     Assert.assertEquals(null, dto.getId()); // OrderEntity的id为int类型,而PropWithDiffType的id为Integer类型,不拷贝  
  10.     Assert.assertEquals("orderName", dto.getName());  
  11. }  

结论:名称相同而类型不同的属性不会被拷贝。 

注意:即使源类型是 原始类型(int, short和char等),目标类型是其 包装类型(Integer, Short和Character等),或反之:都 不会被拷贝。 

3. 源类和目标类有相同的属性(两者的getter都存在),但目标类的setter不存在 
Java代码   收藏代码
  1. @Test  
  2. public void targetLackOfSetterCopyTest() {  
  3.     OrderEntity entity = new OrderEntity();  
  4.     entity.setId(1);  
  5.     entity.setName("orderName");  
  6.     final BeanCopier copier = BeanCopier.create(OrderEntity.class, LackOfSetter.classfalse);  // 抛NullPointerException  
  7.     LackOfSetter dto = new LackOfSetter();  
  8.     copier.copy(entity, dto, null);  
  9. }  

结论:创建BeanCopier的时候抛异常。 

导致异常的原因是BeanCopier类的第128~133行 
Java代码   收藏代码
  1. for (int i = 0; i < setters.length; i++) { // 遍历目标类的属性描述集  
  2.     PropertyDescriptor setter = setters[i];  
  3.     PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName()); // 从源类获取和目标类属性名称相同的属性描述  
  4.     if (getter != null) {  
  5.         MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod()); // 获取源类属性的getter方法  
  6.         MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod()); // 获取目标类属性的setter方法。LackOfSetter类name属性的setter方法没有,所以报错  


4. 源类或目标类的setter比getter少 
Java代码   收藏代码
  1. @Test  
  2. public void sourceLackOfSetterCopyTest() {  
  3.     LackOfSetter source = new LackOfSetter(1"throne");  
  4.     final BeanCopier copier = BeanCopier.create(LackOfSetter.class, OrderDto.classfalse);  
  5.     OrderDto dto = new OrderDto();  
  6.     copier.copy(source, dto, null);  
  7.     Assert.assertEquals(1, dto.getId());  
  8.     Assert.assertEquals("throne", dto.getName());  
  9. }  

结论:拷贝OK。此时的setter多余,但不会报错。 

总结: 

1. BeanCopier只拷贝名称和类型都相同的属性。 

2. 当目标类的setter数目比getter少时,创建BeanCopier会失败而导致拷贝不成功。

二、自定义转换器

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

Java代码  收藏代码
  1. public class AccountEntity {  
  2.     private int id;  
  3.     private Timestamp createTime;  
  4.     private BigDecimal balance;  
  5.     // Getters and setters are omitted  
  6. }  

Java代码  收藏代码
  1. public class AccountDto {  
  2.     private int id;  
  3.     private String name;  
  4.     private String createTime;  
  5.     private String balance;  
  6.     // Getters and setters are omitted  
  7. }  

1. 不使用Converter 
Java代码  收藏代码
  1. public class BeanCopierConverterTest {  
  2.   
  3.     @Test  
  4.     public void noConverterTest() {  
  5.         AccountEntity po = new AccountEntity();  
  6.         po.setId(1);  
  7.         po.setCreateTime(new Timestamp(10043143243L));  
  8.         po.setBalance(BigDecimal.valueOf(4000L));  
  9.         BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.classfalse);  
  10.         AccountDto dto = new AccountDto();  
  11.         copier.copy(po, dto, null);  
  12.         Assert.assertNull(dto.getCreateTime()); // 类型不同,未拷贝  
  13.         Assert.assertNull(dto.getBalance()); // 类型不同,未拷贝  
  14.     }  
  15. }  

2. 使用Converter 
基于目标对象的属性出发,如果源对象有相同名称的属性,则调一次convert方法: 
Java代码  收藏代码
  1. package net.sf.cglib.core;  
  2.   
  3. public interface Converter {  
  4.     // value 源对象属性,target 目标对象属性类,context 目标对象setter方法名  
  5.     Object convert(Object value, Class target, Object context);  
  6. }  
Java代码  收藏代码
  1. @Test  
  2. public void converterTest() {  
  3.     AccountEntity po = new AccountEntity();  
  4.     po.setId(1);  
  5.     po.setCreateTime(Timestamp.valueOf("2014-04-12 16:16:15"));  
  6.     po.setBalance(BigDecimal.valueOf(4000L));  
  7.     BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.classtrue);  
  8.     AccountConverter converter = new AccountConverter();  
  9.     AccountDto dto = new AccountDto();  
  10.     copier.copy(po, dto, converter);  
  11.     Assert.assertEquals("2014-04-12 16:16:15", dto.getCreateTime());  
  12.     Assert.assertEquals("4000", dto.getBalance());  
  13. }  
  14.   
  15. static class AccountConverter implements Converter {  
  16.   
  17.     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  18.   
  19.     @SuppressWarnings("rawtypes")  
  20.     @Override  
  21.     public Object convert(Object value, Class target, Object context) {  
  22.         if (value instanceof Integer) {  
  23.             return (Integer) value;  
  24.         } else if (value instanceof Timestamp) {  
  25.             Timestamp date = (Timestamp) value;  
  26.             return sdf.format(date);  
  27.         } else if (value instanceof BigDecimal) {  
  28.             BigDecimal bd = (BigDecimal) value;  
  29.             return bd.toPlainString();  
  30.         }  
  31.         return null;  
  32.     }  
  33. }  
注:一旦使用Converter,BeanCopier只使用Converter定义的规则去拷贝属性,所以在convert方法中要考虑所有的属性。

三、缓存BeanCopier提升性能

BeanCopier拷贝速度快,性能瓶颈出现在创建BeanCopier实例的过程中。 所以,把创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能:
Java代码 复制代码 收藏代码
  1. public class CachedBeanCopier {  
  2.   
  3.     static final Map<String, BeanCopier> BEAN_COPIERS = new HashMap<String, BeanCopier>();  
  4.   
  5.     public static void copy(Object srcObj, Object destObj) {  
  6.         String key = genKey(srcObj.getClass(), destObj.getClass());  
  7.         BeanCopier copier = null;  
  8.         if (!BEAN_COPIERS.containsKey(key)) {  
  9.             copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);  
  10.             BEAN_COPIERS.put(key, copier);  
  11.         } else {  
  12.             copier = BEAN_COPIERS.get(key);  
  13.         }  
  14.         copier.copy(srcObj, destObj, null);  
  15.     }  
  16.   
  17.     private static String genKey(Class<?> srcClazz, Class<?> destClazz) {  
  18.         return srcClazz.getName() + destClazz.getName();  
  19.     }  
  20. }  
  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值