cglib BeanCopier的使用


一、概述

  選擇Cglib的BeanCopier進行Bean拷貝的理由是,其性能要比Spring的BeanUtils,Apache的BeanUtils和PropertyUtils要好很多,尤其是數據量比較大的情況下。

  之前的一篇文章:Easy-mapper教程——模型轉換工具 提到了Cglib的BeanCopier使用ASM字節碼生成技術,所以性能會非常好。

  下面的文章內容直接整理自網上資源,有錯誤之處敬請諒解,后續再整理。

二、相關使用案例

1、引入maven依賴

<dependency>  
            <groupId>asm</groupId>  
            <artifactId>asm</artifactId>  
            <version>3.3.1</version>  
        </dependency>  
        <dependency>  
            <groupId>asm</groupId>  
            <artifactId>asm-commons</artifactId>  
            <version>3.3.1</version>  
        </dependency>  
        <dependency>  
            <groupId>asm</groupId>  
            <artifactId>asm-util</artifactId>  
            <version>3.3.1</version>  
        </dependency>  
        <dependency>  
            <groupId>cglib</groupId>  
            <artifactId>cglib-nodep</artifactId>  
            <version>2.2.2</version>  
        </dependency>  

2、使用Demo

public class OrderEntity {  
    private int id;  
    private String name;  
    // Getters and setters are omitted  
}  
public class OrderDto {  
    private int id;  
    private String name;  
    // Getters and setters are omitted  
}  
public class PropWithDiffType {  
    private Integer id;  
    private String name;  
    // Getters and setters are omitted  
}  
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;  
    // }  
}  

2.1屬性名稱、類型都相同: 

@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.2屬性名稱相同、類型不同:

@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等),或反之:都不會被拷貝。 

2.3源類和目標類有相同的屬性(兩者的getter都存在),但目標類的setter不存在 

@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行 

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方法沒有,所以報錯  

3、小結: 

1. BeanCopier只拷貝名稱和類型都相同的屬性。 

2. 當目標類的setter數目比getter少時,創建BeanCopier會失敗而導致拷貝不成功。

三、自定義Converter轉換器

當源和目標類的屬性類型不同時,不能拷貝該屬性,此時我們可以通過實現Converter接口來自定義轉換器

源類和目標類: 

public class AccountEntity {  
    private int id;  
    private Timestamp createTime;  
    private BigDecimal balance;  
    // Getters and setters are omitted  
} 
public class AccountDto {  
    private int id;  
    private String name;  
    private String createTime;  
    private String balance;  
    // Getters and setters are omitted  
}  

1、不使用Converter 

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()); // 類型不同,未拷貝  
    }  
}  

2、使用Converter 

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

package net.sf.cglib.core;  
  
public interface Converter {  
    // value 源對象屬性,target 目標對象屬性類,context 目標對象setter方法名  
    Object convert(Object value, Class target, Object context);  
}  
@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方法中要考慮所有的屬性。

四、提供個工具類

package com.yusj.utils;  
  
import java.util.HashMap;  
import java.util.Map;  
  
import net.sf.cglib.beans.BeanCopier;  
  
/** 
 *  
 * 將beancopier做成靜態類,方便拷貝 
 * <br>創建日期:2015年12月1日 
 * <br><b>Copyright 2015 UTOUU All Rights Reserved</b> 
 * @author yushaojian 
 * @since 1.0 
 * @version 1.0 
 */  
public class CglibBeanCopierUtils {  
      
    /** 
     *  
     */  
    public static Map<String, BeanCopier> beanCopierMap = new HashMap<String, BeanCopier>();  
      
    /**  
    * @Title: copyProperties  
    * @Description: TODO(bean屬性轉換)  
    * @param source 資源類 
    * @param target  目標類  
    * @author yushaojian 
    * @date 2015年11月25日下午4:56:44 
    */  
    public static void copyProperties(Object source,Object target){  
        String beanKey = generateKey(source.getClass(),target.getClass());  
        BeanCopier copier = null;  
        if (!beanCopierMap.containsKey(beanKey)) {  
            copier = BeanCopier.create(source.getClass(), target.getClass(), false);  
            beanCopierMap.put(beanKey, copier);  
        }else {  
            copier = beanCopierMap.get(beanKey);  
        }  
        copier.copy(source, target, null);  
    }  
    private static String generateKey(Class<?>class1,Class<?>class2){  
        return class1.toString() + class2.toString();  
    }  
    /*注: 
    (1)相同屬性名,且類型不匹配時候的處理,ok,但是未滿足的屬性不拷貝; 
    (2)get和set方法不匹配的處理,創建拷貝的時候報錯,無法拷貝任何屬性(當且僅當sourceClass的get方法超過set方法時出現) 
    (3)BeanCopier  
    初始化例子:BeanCopier copier = BeanCopier.create(Source.class, Target.class, useConverter=true) 
    第三個參數userConverter,是否開啟Convert,默認BeanCopier只會做同名,同類型屬性的copier,否則就會報錯. 
    copier = BeanCopier.create(source.getClass(), target.getClass(), false); 
    copier.copy(source, target, null); 
    (4)修復beanCopier對set方法強限制的約束 
    改寫net.sf.cglib.beans.BeanCopier.Generator.generateClass(ClassVisitor)方法 
    將133行的 
    MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod()); 
    預先存一個names2放入 
     109        Map names2 = new HashMap(); 
     110        for (int i = 0; i < getters.length; ++i) { 
     111          names2.put(setters[i].getName(), getters[i]); 
                } 
    調用這行代碼前判斷查詢下,如果沒有改writeMethod則忽略掉該字段的操作,這樣就可以避免異常的發生。*/  
  
}  

 

 

 

 

 參考文章:

https://blog.csdn.net/liangrui1988/article/details/41802275

https://ysj5125094.iteye.com/blog/2260885

http://cglib.sourceforge.net/apidocs/net/sf/cglib/beans/BeanCopier.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM