使用CGlib實現Bean拷貝(BeanCopier)


 


  在做業務的時候,我們有時為了隔離變化,會將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。 

2. 屬性名稱相同、類型不同: 

復制代碼
@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());
}
復制代碼

2. 使用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<User> 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<UserDto> 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實例放到緩存中,下次可以直接獲取,提升性能:

依賴:

復制代碼
<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.10</version>
    </dependency>
    <dependency>
        <groupId>com.esotericsoftware</groupId>
        <artifactId>reflectasm</artifactId>
        <version>1.11.7</version>
    </dependency>
</dependencies>
復制代碼

 封裝工具

復制代碼
public class WrapperBeanCopier {

    private WrapperBeanCopier() {
        //do nothing
    }

    private static final Map<String, BeanCopier> BEAN_COPIER_CACHE = new ConcurrentHashMap<>();

    private static final Map<String, ConstructorAccess> 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> T copyProperties(Object source, Class<T> 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 <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClass) {
        if (sourceList == null || sourceList.isEmpty()) {
            return Collections.emptyList();
        }
        ConstructorAccess<T> constructorAccess = getConstructorAccess(targetClass);
        List<T> 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 <T> ConstructorAccess<T> getConstructorAccess(Class<T> targetClass) {
        ConstructorAccess<T> 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;
    }

}
復制代碼
 
 


免責聲明!

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



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