使用CGlib实现Bean拷贝(BeanCopier)「建议收藏」

使用CGlib实现Bean拷贝(BeanCopier)「建议收藏」在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要BeanCopier来帮助我们。BeanCopier其实已经有很多开源版本,例如DozerMapper、ApacheBeanUtils、Spring、JoddBeanU…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要BeanCopier来帮助我们。BeanCopier其实已经有很多开源版本,例如DozerMapper、Apache BeanUtils、Spring、Jodd BeanUtils甚至是Cglib都提供了这样的功能。下面介绍Cglib的BeanCopier的使用。

1、通过拷贝bean对象来测试BeanCopier的基本使用和特性
Java代码 收藏代码
public class OrderEntity {

private int id;
private String name;
// Getters and setters are omitted
}

Java代码 收藏代码
public class OrderDto {

private int id;
private String name;
// Getters and setters are omitted
}

Java代码 收藏代码
public class PropWithDiffType {

private Integer id;
private String name;
// Getters and setters are omitted
}

Java代码 收藏代码
public class LackOfSetter {

private int id;
private String name;

public LackOfSetter() {  
}  

public LackOfSetter(int id, String name) {  
    this.id = id;  
    this.name = name;  
}  
// Getters and setters are omitted  
// public void setName(String name) {  
//  this.name = name;  
// }  

}

  1. 属性名称、类型都相同:
    Java代码 收藏代码
    @Test
    public void normalCopyTest() {

    OrderEntity entity = new OrderEntity();
    entity.setId(1);
    entity.setName(“orderName”);
    final BeanCopier copier = BeanCopier.create(OrderEntity.class, OrderDto.class, false);
    OrderDto dto = new OrderDto();
    copier.copy(entity, dto, null);
    Assert.assertEquals(1, dto.getId());
    Assert.assertEquals(“orderName”, dto.getName());
    }

结论:拷贝OK。
2. 属性名称相同、类型不同:

Java代码 收藏代码
@Test
public void sameNameDifferentTypeCopyTest() {

OrderEntity entity = new OrderEntity();
entity.setId(1);
entity.setName(“orderName”);
final BeanCopier copier = BeanCopier.create(OrderEntity.class, PropWithDiffType.class, false);
PropWithDiffType dto = new PropWithDiffType();
copier.copy(entity, dto, null);
Assert.assertEquals(null, dto.getId()); // OrderEntity的id为int类型,而PropWithDiffType的id为Integer类型,不拷贝
Assert.assertEquals(“orderName”, dto.getName());
}

结论:名称相同而类型不同的属性不会被拷贝。

注意:即使源类型是原始类型(int, short和char等),目标类型是其包装类型(Integer, Short和Character等),或反之:都不会被拷贝。

  1. 源类和目标类有相同的属性(两者的getter都存在),但目标类的setter不存在
    Java代码 收藏代码
    @Test
    public void targetLackOfSetterCopyTest() {

    OrderEntity entity = new OrderEntity();
    entity.setId(1);
    entity.setName(“orderName”);
    final BeanCopier copier = BeanCopier.create(OrderEntity.class, LackOfSetter.class, false); // 抛NullPointerException
    LackOfSetter dto = new LackOfSetter();
    copier.copy(entity, dto, null);
    }

结论:创建BeanCopier的时候抛异常。

导致异常的原因是BeanCopier类的第128~133行
Java代码 收藏代码
for (int i = 0; i < setters.length; i++) { // 遍历目标类的属性描述集
PropertyDescriptor setter = setters[i];
PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName()); // 从源类获取和目标类属性名称相同的属性描述
if (getter != null) {

MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod()); // 获取源类属性的getter方法
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod()); // 获取目标类属性的setter方法。LackOfSetter类name属性的setter方法没有,所以报错

  1. 源类或目标类的setter比getter少
    Java代码 收藏代码
    @Test
    public void sourceLackOfSetterCopyTest() {

    LackOfSetter source = new LackOfSetter(1, “throne”);
    final BeanCopier copier = BeanCopier.create(LackOfSetter.class, OrderDto.class, false);
    OrderDto dto = new OrderDto();
    copier.copy(source, dto, null);
    Assert.assertEquals(1, dto.getId());
    Assert.assertEquals(“throne”, dto.getName());
    }

结论:拷贝OK。此时的setter多余,但不会报错。

总结:

  1. BeanCopier只拷贝名称和类型都相同的属性。

  2. 当目标类的setter数目比getter少时,创建BeanCopier会失败而导致拷贝不成功。
    二、自定义转换器
    当源和目标类的属性类型不同时,不能拷贝该属性,此时我们可以通过实现Converter接口来自定义转换器
    源类和目标类:

Java代码 收藏代码
public class AccountEntity {

private int id;
private Timestamp createTime;
private BigDecimal balance;
// Getters and setters are omitted
}

Java代码 收藏代码
public class AccountDto {

private int id;
private String name;
private String createTime;
private String balance;
// Getters and setters are omitted
}

  1. 不使用Converter
    Java代码 收藏代码
    public class BeanCopierConverterTest {

    @Test
    public void noConverterTest() {

    AccountEntity po = new AccountEntity();
    po.setId(1);
    po.setCreateTime(new Timestamp(10043143243L));
    po.setBalance(BigDecimal.valueOf(4000L));
    BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.class, false);
    AccountDto dto = new AccountDto();
    copier.copy(po, dto, null);
    Assert.assertNull(dto.getCreateTime()); // 类型不同,未拷贝
    Assert.assertNull(dto.getBalance()); // 类型不同,未拷贝
    }
    }

  2. 使用Converter
    基于目标对象的属性出发,如果源对象有相同名称的属性,则调一次convert方法:
    Java代码 收藏代码
    package net.sf.cglib.core;

public interface Converter {

// value 源对象属性,target 目标对象属性类,context 目标对象setter方法名
Object convert(Object value, Class target, Object context);
}
Java代码 收藏代码
@Test
public void converterTest() {

AccountEntity po = new AccountEntity();
po.setId(1);
po.setCreateTime(Timestamp.valueOf(“2014-04-12 16:16:15”));
po.setBalance(BigDecimal.valueOf(4000L));
BeanCopier copier = BeanCopier.create(AccountEntity.class, AccountDto.class, true);
AccountConverter converter = new AccountConverter();
AccountDto dto = new AccountDto();
copier.copy(po, dto, converter);
Assert.assertEquals(“2014-04-12 16:16:15”, dto.getCreateTime());
Assert.assertEquals(“4000”, dto.getBalance());
}

static class AccountConverter implements Converter {

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  

@SuppressWarnings("rawtypes")  
@Override  
public Object convert(Object value, Class target, Object context) {  
    if (value instanceof Integer) {  
        return (Integer) value;  
    } else if (value instanceof Timestamp) {  
        Timestamp date = (Timestamp) value;  
        return sdf.format(date);  
    } else if (value instanceof BigDecimal) {  
        BigDecimal bd = (BigDecimal) value;  
        return bd.toPlainString();  
    }  
    return null;  
}  

}
注:一旦使用Converter,BeanCopier只使用Converter定义的规则去拷贝属性,所以在convert方法中要考虑所有的属性。
三、缓存BeanCopier提升性能
BeanCopier拷贝速度快,性能瓶颈出现在创建BeanCopier实例的过程中。 所以,把创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能:
Java代码 复制代码 收藏代码
public class CachedBeanCopier {

static final Map<String, BeanCopier> BEAN_COPIERS = new HashMap<String, BeanCopier>();  

public static void copy(Object srcObj, Object destObj) {  
    String key = genKey(srcObj.getClass(), destObj.getClass());  
    BeanCopier copier = null;  
    if (!BEAN_COPIERS.containsKey(key)) {  
        copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);  
        BEAN_COPIERS.put(key, copier);  
    } else {  
        copier = BEAN_COPIERS.get(key);  
    }  
    copier.copy(srcObj, destObj, null);  
}  

private static String genKey(Class<?> srcClazz, Class<?> destClazz) {  
    return srcClazz.getName() + destClazz.getName();  
}  

}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/197063.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • C# FindWindowEx用法

    C# FindWindowEx用法2010-11-2809:51:18|  分类: 程序编程|字号 订阅 函数功能:该函数获得一个窗口的句柄,该窗口的类名和窗口名与给定的字符串相匹配。这个函数查找子窗口,从排在给定的子窗口后面的下一个子窗口开始。在查找时不区分大小写。   函数原型:HWNDFindWindowEx(HWNDhwndParent,HWNDhwndChildAfter

    2022年5月31日
    258
  • docker下载安装教程_docker安装macos

    docker下载安装教程_docker安装macos前言Docker提供轻量的虚拟化,你能够从Docker获得一个额外抽象层,你能够在单台机器上运行多个Docker微容器,而每个微容器里都有一个微服务或独立应用,例如你可以将Tomcat运行在一个D

    2022年7月31日
    3
  • 二分查找判定树(二分查找树平均查找长度)

    如果data[0]等于一组数据中最小的,那么就会增加查找的时间复杂度。平衡二叉树(追求极致的平衡),现实需求很难满足,红黑数孕育而生

    2022年4月11日
    177
  • ntp服务器ntp协议时间戳,计算机的时钟(一):NTP 协议[通俗易懂]

    ntp服务器ntp协议时间戳,计算机的时钟(一):NTP 协议[通俗易懂]本系列文章主要介绍计算机系统中时钟的处理。主要内容包含NTP,Lamport逻辑时钟,向量时钟,TrueTime等。本文是第一篇,介绍NTP协议。电脑的时钟不知道你注意过没有,假如隔了好几天打开你的电脑,任务栏的时间依然是显示正确的,即使你的电脑没有联网,这是如何做到的?post-quartz.png计算机的主板上有一个石英晶体振荡器和一个纽扣电池。石英晶体振荡器的频率是32768Hz每秒。在通电…

    2022年10月12日
    0
  • 帝国CMS实现【加入收藏】与【设为首页】的方法[通俗易懂]

    帝国CMS实现【加入收藏】与【设为首页】的方法[通俗易懂]本文实例讲述了帝国CMS实现加入收藏与设为首页的方法。分享给大家供大家参考。具体实现方法如下:加入收藏,设为首页代码,兼容IE,火狐,谷歌等所有浏览器,复制以下代码到需要显示的地方:<ao

    2022年7月2日
    21
  • 通俗易懂的Mybatis工作原理[通俗易懂]

    作为半自动的ORM框架,Mybatis被越来越多的企业接受。搞清楚它的工作原理以及底层实现,对于开发者可事半功倍。很多文章都是使用大批量的源码流程去分析原理。对于有源码阅读功底的开发者,也许还能招架住,但还是不直观。我以前的很多文章都是这么做的,后来有朋友私信建议说,这些文章类似于个人笔记,只能自己阅读,不利于分享,所以,本文将尝试采用通俗易懂的白话文带领大家认识一下Mybatis的工作原理。(PS:大家可以设想,如果自己在开发Mybatis,该如何设计好Mybatis的功能呢?)一...

    2022年4月17日
    38

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号