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;
        }
    
    }

     

  • 相关阅读:
    期中架构实现步骤
    安装centos以及优化步骤
    inotify+rsync实现实时热备
    [转]ubuntu安装vncserver实现图形化访问
    [转]电烙铁的使用小技巧
    彻底解决 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
    解读系统托盘图标隐藏(删除)
    一个小公式帮你轻松将IP地址从10进制转到2进制
    [查阅]Dalvik opcodes
    [查阅]MSIL Instruction Set
  • 原文地址:https://www.cnblogs.com/winner-0715/p/10117282.html
Copyright © 2011-2022 走看看