使用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. 屬性名稱、類型都相同:  app

@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。 ide

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接口來自定義轉換器
源類和目標類:this

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 spa

@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方法: code

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方法中要考慮全部的屬性。orm

封裝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; } }
相關文章
相關標籤/搜索