天天看點

使用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代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  1. public class OrderEntity {  
  2.     private int id;  
  3.     private String name;  
  4.     // Getters and setters are omitted  
  5. }  

Java代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  1. public class OrderDto {  
  2.     private int id;  
  3.     private String name;  
  4.     // Getters and setters are omitted  
  5. }  

Java代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  1. public class PropWithDiffType {  
  2.     private Integer id;  
  3.     private String name;  
  4.     // Getters and setters are omitted  
  5. }  

Java代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  1. public class LackOfSetter {  
  2.     private int id;  
  3.     private String name;  
  4.     public LackOfSetter() {  
  5.     }  
  6.     public LackOfSetter(int id, String name) {  
  7.         this.id = id;  
  8.         this.name = name;  
  9.     }  
  10.     // Getters and setters are omitted  
  11.     // public void setName(String name) {  
  12.     //  this.name = name;  
  13.     // }  
  14. }  

1. 屬性名稱、類型都相同: 

Java代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  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.class, false);  
  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代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  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.class, false);  
  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代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  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.class, false);  // 抛NullPointerException  
  7.     LackOfSetter dto = new LackOfSetter();  
  8.     copier.copy(entity, dto, null);  
  9. }  

結論:建立BeanCopier的時候抛異常。 

導緻異常的原因是BeanCopier類的第128~133行 

Java代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  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代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  1. @Test  
  2. public void sourceLackOfSetterCopyTest() {  
  3.     LackOfSetter source = new LackOfSetter(1, "throne");  
  4.     final BeanCopier copier = BeanCopier.create(LackOfSetter.class, OrderDto.class, false);  
  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代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  1. public class AccountEntity {  
  2.     private int id;  
  3.     private Timestamp createTime;  
  4.     private BigDecimal balance;  
  5.     // Getters and setters are omitted  
  6. }  

Java代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  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代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  1. public class BeanCopierConverterTest {  
  2.     @Test  
  3.     public void noConverterTest() {  
  4.         AccountEntity po = new AccountEntity();  
  5.         po.setId(1);  
  6.         po.setCreateTime(new Timestamp(10043143243L));  
  7.         po.setBalance(BigDecimal.valueOf(4000L));  
  8.         BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.class, false);  
  9.         AccountDto dto = new AccountDto();  
  10.         copier.copy(po, dto, null);  
  11.         Assert.assertNull(dto.getCreateTime()); // 類型不同,未拷貝  
  12.         Assert.assertNull(dto.getBalance()); // 類型不同,未拷貝  
  13.     }  
  14. }  

2. 使用Converter 

基于目标對象的屬性出發,如果源對象有相同名稱的屬性,則調一次convert方法: 

Java代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  1. package net.sf.cglib.core;  
  2. public interface Converter {  
  3.     // value 源對象屬性,target 目标對象屬性類,context 目标對象setter方法名  
  4.     Object convert(Object value, Class target, Object context);  
  5. }  

Java代碼  

使用CGlib實作Bean拷貝(BeanCopier)
  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.class, true);  
  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. static class AccountConverter implements Converter {  
  15.     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  16.     @SuppressWarnings("rawtypes")  
  17.     @Override  
  18.     public Object convert(Object value, Class target, Object context) {  
  19.         if (value instanceof Integer) {  
  20.             return (Integer) value;  
  21.         } else if (value instanceof Timestamp) {  
  22.             Timestamp date = (Timestamp) value;  
  23.             return sdf.format(date);  
  24.         } else if (value instanceof BigDecimal) {  
  25.             BigDecimal bd = (BigDecimal) value;  
  26.             return bd.toPlainString();  
  27.         }  
  28.         return null;  
  29.     }  
  30. }  

注:一旦使用Converter,BeanCopier隻使用Converter定義的規則去拷貝屬性,是以在convert方法中要考慮所有的屬性。

三、緩存BeanCopier提升性能

BeanCopier拷貝速度快,性能瓶頸出現在建立BeanCopier執行個體的過程中。 是以,把建立過的BeanCopier執行個體放到緩存中,下次可以直接擷取,提升性能:

Java代碼

使用CGlib實作Bean拷貝(BeanCopier)
使用CGlib實作Bean拷貝(BeanCopier)
使用CGlib實作Bean拷貝(BeanCopier)
  1. public class CachedBeanCopier {  
  2.     static final Map<String, BeanCopier> BEAN_COPIERS = new HashMap<String, BeanCopier>();  
  3.     public static void copy(Object srcObj, Object destObj) {  
  4.         String key = genKey(srcObj.getClass(), destObj.getClass());  
  5.         BeanCopier copier = null;  
  6.         if (!BEAN_COPIERS.containsKey(key)) {  
  7.             copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);  
  8.             BEAN_COPIERS.put(key, copier);  
  9.         } else {  
  10.             copier = BEAN_COPIERS.get(key);  
  11.         }  
  12.         copier.copy(srcObj, destObj, null);  
  13.     }  
  14.     private static String genKey(Class<?> srcClazz, Class<?> destClazz) {  
  15.         return srcClazz.getName() + destClazz.getName();  
  16.     }  
  17. }