在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要BeanCopier来帮助我们。选择Cglib的BeanCopier进行Bean拷贝的理由是,其性能要比Spring的BeanUtils,Apache的BeanUtils和PropertyUtils要好很多,尤其是数据量比较大的情况下。

BeanCopier基本用法

  1. public class User {
  2. private int age;
  3. private String name;
  4.  
  5. public int getAge() {
  6. return age;
  7. }
  8.  
  9. public void setAge(int age) {
  10. this.age = age;
  11. }
  12.  
  13. public String getName() {
  14. return name;
  15. }
  16.  
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. }
  1. public class UserDto {
  2. private int age;
  3. private String name;
  4.  
  5. public int getAge() {
  6. return age;
  7. }
  8.  
  9. public void setAge(int age) {
  10. this.age = age;
  11. }
  12.  
  13. public String getName() {
  14. return name;
  15. }
  16.  
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. }
  1. public class UserWithDiffType {
  2. private Integer age;
  3. private String name;
  4.  
  5. public Integer getAge() {
  6. return age;
  7. }
  8.  
  9. public void setAge(Integer age) {
  10. this.age = age;
  11. }
  12.  
  13. public String getName() {
  14. return name;
  15. }
  16.  
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. }

1. 属性名称、类型都相同:

  1. @Test
  2. public void normalCopyTest() {
  3. // create(Class source, Class target, boolean useConverter)
  4. final BeanCopier beanCopier = BeanCopier.create(User.class, UserDto.class, false);
  5. User user = new User();
  6. user.setAge(10);
  7. user.setName("zhangsan");
  8. UserDto userDto = new UserDto();
  9. beanCopier.copy(user, userDto, null);
  10. Assert.assertEquals(10, userDto.getAge());
  11. Assert.assertEquals("zhangsan", userDto.getName());
  12. }

结论:属性名称相同类型相同的属性拷贝OK

2. 属性名称相同、类型不同:

  1. @Test
  2. public void normalCopyTest() {
  3. // create(Class source, Class target, boolean useConverter)
  4. final BeanCopier beanCopier = BeanCopier.create(User.class, UserWithDiffType.class, false);
  5. User user = new User();
  6. user.setAge(10);
  7. user.setName("zhangsan");
  8. UserWithDiffType userDto = new UserWithDiffType();
  9. beanCopier.copy(user, userDto, null);
  10. Assert.assertEquals(null, userDto.getAge());
  11. Assert.assertEquals("zhangsan", userDto.getName());
  12. }

结论:属性名称相同而类型不同的属性不会被拷贝。  
注意:即使源类型是原始类型(int, short和char等),目标类型是其包装类型(Integer, Short和Character等),或反之:都 不会被拷贝。

总结:  
BeanCopier只拷贝名称和类型都相同的属性

自定义转换器

当源和目标类的属性类型不同时,不能拷贝该属性,此时我们可以通过实现Converter接口来自定义转换器
源类和目标类:

  1. public class Account {
  2. private int id;
  3. private Date createTime;
  4. private BigDecimal balance;
  5.  
  6. public int getId() {
  7. return id;
  8. }
  9.  
  10. public void setId(int id) {
  11. this.id = id;
  12. }
  13.  
  14. public Date getCreateTime() {
  15. return createTime;
  16. }
  17.  
  18. public void setCreateTime(Date createTime) {
  19. this.createTime = createTime;
  20. }
  21.  
  22. public BigDecimal getBalance() {
  23. return balance;
  24. }
  25.  
  26. public void setBalance(BigDecimal balance) {
  27. this.balance = balance;
  28. }
  29. }
  1. public class AccountDto {
  2. private int id;
  3. private String createTime;
  4. private String balance;
  5.  
  6. public int getId() {
  7. return id;
  8. }
  9.  
  10. public void setId(int id) {
  11. this.id = id;
  12. }
  13.  
  14. public String getCreateTime() {
  15. return createTime;
  16. }
  17.  
  18. public void setCreateTime(String createTime) {
  19. this.createTime = createTime;
  20. }
  21.  
  22. public String getBalance() {
  23. return balance;
  24. }
  25.  
  26. public void setBalance(String balance) {
  27. this.balance = balance;
  28. }
  29. }

1. 不使用Converter

  1. @Test
  2. public void noConverterTest() {
  3. Account po = new Account();
  4. po.setId(1);
  5. po.setCreateTime(new Date());
  6. po.setBalance(BigDecimal.valueOf(4000L));
  7. BeanCopier copier = BeanCopier.create(Account.class, AccountDto.class, false);
  8. AccountDto dto = new AccountDto();
  9. copier.copy(po, dto, null);
  10. // 类型不同,未拷贝
  11. Assert.assertNull(dto.getBalance());
  12. // 类型不同,未拷贝
  13. Assert.assertNull(dto.getCreateTime());
  14. }

2. 使用Converter 
基于目标对象的属性出发,如果源对象有相同名称的属性,则调一次convert方法:

  1. public class TestCase {
  2.  
  3. @Test
  4. public void noConverterTest() {
  5. Account po = new Account();
  6. po.setId(1);
  7. po.setCreateTime(new Date());
  8. po.setBalance(BigDecimal.valueOf(4000L));
  9. BeanCopier copier = BeanCopier.create(Account.class, AccountDto.class, true);
  10. AccountDto dto = new AccountDto();
  11. AccountConverter converter = new AccountConverter();
  12. copier.copy(po, dto, converter);
  13. // 类型不同,未拷贝
  14. Assert.assertEquals("4000", dto.getBalance());
  15. // 类型不同,未拷贝
  16. Assert.assertEquals("2018-12-13", dto.getCreateTime());
  17. }
  18. }
  19.  
  20. class AccountConverter implements Converter {
  21.  
  22. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  23.  
  24. @SuppressWarnings("rawtypes")
  25. @Override
  26. public Object convert(Object source, Class target, Object context) {
  27. if (source instanceof Integer) {
  28. return (Integer) source;
  29. } else if (source instanceof Date) {
  30. Date date = (Date) source;
  31. return sdf.format(date);
  32. } else if (source instanceof BigDecimal) {
  33. BigDecimal bd = (BigDecimal) source;
  34. return bd.toPlainString();
  35. }
  36. return null;
  37. }
  38. }

注:一旦使用Converter,BeanCopier只使用Converter定义的规则去拷贝属性,所以在convert方法中要考虑所有的属性。

封装BeanCopier

  1. @Test
  2. public void costTest() {
  3. List<User> list1 = new ArrayList<>(100);
  4. for (int i = 0; i < 100; i++) {
  5. User po = new User();
  6. po.setId(1);
  7. po.setCreateTime(new Date());
  8. po.setBalance(BigDecimal.valueOf(4000L));
  9. list1.add(po);
  10. }
  11. BeanCopier copier = BeanCopier.create(User.class, UserDto.class, false);
  12. long start = System.currentTimeMillis();
  13. List<UserDto> list2 = new ArrayList<>(100);
  14. for (User user : list1) {
  15. UserDto dto = new UserDto();
  16. //BeanUtils.beanCopy(user, dto);
  17. copier.copy(user, dto, null);
  18. list2.add(dto);
  19. }
  20. System.out.printf("took time: %d(ms)%n",System.currentTimeMillis() - start);
  21. }

经过测试,BeanCopier性能是BeanUtils10倍左右。

BeanCopier拷贝速度快,性能瓶颈出现在创建BeanCopier实例的过程中。 所以,把创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能:

依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>cglib</groupId>
  4. <artifactId>cglib</artifactId>
  5. <version>3.2.10</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.esotericsoftware</groupId>
  9. <artifactId>reflectasm</artifactId>
  10. <version>1.11.7</version>
  11. </dependency>
  12. </dependencies>

封装工具

  1. public class WrapperBeanCopier {
  2.  
  3. private WrapperBeanCopier() {
  4. //do nothing
  5. }
  6.  
  7. private static final Map<String, BeanCopier> BEAN_COPIER_CACHE = new ConcurrentHashMap<>();
  8.  
  9. private static final Map<String, ConstructorAccess> CONSTRUCTOR_ACCESS_CACHE = new ConcurrentHashMap<>();
  10.  
  11. public static void copyProperties(Object source, Object target) {
  12. BeanCopier copier = getBeanCopier(source.getClass(), target.getClass());
  13. copier.copy(source, target, null);
  14. }
  15.  
  16. private static BeanCopier getBeanCopier(Class sourceClass, Class targetClass) {
  17. String beanKey = generateKey(sourceClass, targetClass);
  18. BeanCopier copier = null;
  19. if (!BEAN_COPIER_CACHE.containsKey(beanKey)) {
  20. copier = BeanCopier.create(sourceClass, targetClass, false);
  21. BEAN_COPIER_CACHE.put(beanKey, copier);
  22. } else {
  23. copier = BEAN_COPIER_CACHE.get(beanKey);
  24. }
  25. return copier;
  26. }
  27.  
  28. /**
  29. * 两个类的全限定名拼接起来构成Key
  30. *
  31. * @param sourceClass
  32. * @param targetClass
  33. * @return
  34. */
  35. private static String generateKey(Class<?> sourceClass, Class<?> targetClass) {
  36. return sourceClass.getName() + targetClass.getName();
  37. }
  38.  
  39. public static <T> T copyProperties(Object source, Class<T> targetClass) {
  40. T t = null;
  41. try {
  42. t = targetClass.newInstance();
  43. } catch (InstantiationException | IllegalAccessException e) {
  44. throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
  45. }
  46. copyProperties(source, t);
  47. return t;
  48. }
  49.  
  50. public static <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClass) {
  51. if (sourceList == null || sourceList.isEmpty()) {
  52. return Collections.emptyList();
  53. }
  54. ConstructorAccess<T> constructorAccess = getConstructorAccess(targetClass);
  55. List<T> resultList = new ArrayList<>(sourceList.size());
  56. for (Object o : sourceList) {
  57. T t = null;
  58. try {
  59. t = constructorAccess.newInstance();
  60. copyProperties(o, t);
  61. resultList.add(t);
  62. } catch (Exception e) {
  63. throw new RuntimeException(e);
  64. }
  65. }
  66. return resultList;
  67. }
  68.  
  69. private static <T> ConstructorAccess<T> getConstructorAccess(Class<T> targetClass) {
  70. ConstructorAccess<T> constructorAccess = CONSTRUCTOR_ACCESS_CACHE.get(targetClass.getName());
  71. if (constructorAccess != null) {
  72. return constructorAccess;
  73. }
  74. try {
  75. constructorAccess = ConstructorAccess.get(targetClass);
  76. constructorAccess.newInstance();
  77. CONSTRUCTOR_ACCESS_CACHE.put(targetClass.toString(), constructorAccess);
  78. } catch (Exception e) {
  79. throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
  80. }
  81. return constructorAccess;
  82. }
  83.  
  84. }

使用CGlib实现Bean拷贝(BeanCopier)的更多相关文章

  1. [性能] Bean拷贝工具类性能比较

    Bean拷贝工具类性能比较 引言 几年前做过一个项目,接入新的api接口.为了和api实现解耦,决定将api返回的实体类在本地也建一个.这样做有两个好处 可以在api变更字段的时候保持应用稳定性 可以 ...

  2. 就因为加了Lombok的@Accessors(chain = true),bean拷贝工具类不干活了

    前言 这次新建了一个工程,因为 Lombok 用得很习惯,但以前的话,一般只用了@Data,@AllArgsConstructor,@EqualsAndHashCode等常规注解:那这个Accesso ...

  3. Bean拷贝工具

    Apache BeanUtils Spring BeanUtils cglib BeanCopier Hutool BeanUtil Mapstruct Dozer 1.Apache  BeanUti ...

  4. Java Bean拷贝工具Orika原理解析

    最近面试被问及对象拷贝怎样才能高效,实际上问的就是Orika或者BeanCopier的原理.由于网上对Orika原理的解析并不太多-因此本文重点讲解一下Orika的原理.(Orika是基于JavaBe ...

  5. Bean拷贝

    相当于C#的AutoMapper public class CloneUtils { /** * 拷贝对象 * @param source * @param classType * @return * ...

  6. 【小工具】根据定义的白名单字段进行Bean的拷贝

    背景 Bean的拷贝一直有一些类可以使用,比如Apache的org.apache.commons.beanutils.BeanUtils或者Spring的org.springframework.bea ...

  7. Spring工厂方式创建Bean实例

    创建Bean实例的方式: 1) 通过构造器(有参或无参) 方式: <bean id="" class=""/> 2) 通过静态工厂方法 方式: &l ...

  8. (spring-第10回【IoC基础篇】)InstantiationStrategy--实例化Bean的第三大利器

    Bean的实例化整个过程如下图: : 其中,BeanDefinition加入到注册表中,并由BeanFactoryPostProcessor的实现类处理后,需要由InstantiationStrate ...

  9. spring ApplicationContext中Bean的生命周期

    AbstractApplicationContext Spring的AbstractApplicationContext是ApplicationContext的抽象实现类,该抽象类的refresh方法 ...

随机推荐

  1. awk介绍

    awk 是一个强大的文本处理工具,它将文本逐行读入,并进行切片,默认以空白格为分割符,对单个切片进行分析,处理. 用法: awk '{pattern + action}' {filenames} 尽管 ...

  2. autio的自动播放问题

    最近做年会相关内容,背景音乐插入了,电脑上没问题,移动版就出事了,下面做一下记录  <audio src="" autoplay="autoplay" l ...

  3. sql 分隔字符串函数

    USE [tms]GO/****** Object: UserDefinedFunction [dbo].[fn_ConvertListToTable_Sort] Script Date: 2017/ ...

  4. CentOS6.2(64bit)下mysql5.6.16主从同步配置

    1. 主配置,进入mysql.cnf在[mysqld]下面添加以下配置 [root@localhost ~]# vi /etc/mysql.cnf server-id=1 binlog-format= ...

  5. thinkphp5 行为(钩子)扩展

    行为整理链接 浅谈PHP中的钩子 钩子相当于一个插件,在某些执行顺序上插入进去. 行为可以在写app接口中对所有请求执行到控制器前 执行用户权限判断,sign验证等,这样就不用在每个接口中判断了 注意 ...

  6. 【LeetCode算法-9】Palindrome Number

    LeetCode第9题 Determine whether an integer is a palindrome. An integer is a palindrome when it reads t ...

  7. [洛谷P1880][NOI1995]石子合并

    区间DP模板题 区间DP模板Code: ;len<=n;len++) { ;i<=*n-;i++) //区间左端点 { ; //区间右端点 for(int k=i;k<j;k++) ...

  8. Django 学习第四天——Django 模板标签

    一.模板标签: 作用:标签在渲染的过程中提供任意的逻辑:例如 if for...in... 等 标签语法:由 {% %} 来定义的:例如:{% tag %}xxx{% endtag %} 常用标签: ...

  9. SQL 客户端查看

    SELECT *FROM master.dbo.sysprocesses (nolock)ORDER BY hostname DESC

  10. 移动端小坑:用户长按H5文字出现复制

    禁止复制方法:*{ -webkit-user-select: none;/*禁用手机浏览器的用户选择功能 */ -moz-user-select: none; -webkit-touch-callou ...