zoukankan      html  css  js  c++  java
  • 使用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;
        }
    
    }
    复制代码
     
     
  • 相关阅读:
    HDU4366 Successor 线段树+预处理
    POJ2823 Sliding Window 单调队列
    HDU寻找最大值 递推求连续区间
    UVA846 Steps 二分查找
    HDU3415 Max Sum of MaxKsubsequence 单调队列
    HDU时间挑战 树状数组
    UVA10168 Summation of Four Primes 哥德巴赫猜想
    UESTC我要长高 DP优化
    HDUChess 递推
    HDU4362 Dragon Ball DP+优化
  • 原文地址:https://www.cnblogs.com/sunny-miss/p/13138038.html
Copyright © 2011-2022 走看看