cglib BeanCopier的使用

cglib BeanCopier的使用一、概述  选择Cglib的BeanCopier进行Bean拷贝的理由是,其性能要比Spring的BeanUtils,Apache的BeanUtils和PropertyUtils要好很多,尤其是数据量比较大的情况下。  之前的一篇文章:Easy-mapper教程——模型转换工具提到了Cglib的BeanCopier使用ASM字节码生成技术,所以性能会非常好。  下面的文章内容直接整理…

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

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

一、概述

  选择Cglib的BeanCopier进行Bean拷贝的理由是,其性能要比Spring的BeanUtils,Apache的BeanUtils和PropertyUtils要好很多,尤其是数据量比较大的情况下。

  之前的一篇文章:Easy-mapper教程——模型转换工具 提到了Cglib的BeanCopier使用ASM字节码生成技术,所以性能会非常好。

  下面的文章内容直接整理自网上资源,有错误之处敬请谅解,后续再整理。

二、相关使用案例

1、引入maven依赖

<dependency>  
            <groupId>asm</groupId>  
            <artifactId>asm</artifactId>  
            <version>3.3.1</version>  
        </dependency>  
        <dependency>  
            <groupId>asm</groupId>  
            <artifactId>asm-commons</artifactId>  
            <version>3.3.1</version>  
        </dependency>  
        <dependency>  
            <groupId>asm</groupId>  
            <artifactId>asm-util</artifactId>  
            <version>3.3.1</version>  
        </dependency>  
        <dependency>  
            <groupId>cglib</groupId>  
            <artifactId>cglib-nodep</artifactId>  
            <version>2.2.2</version>  
        </dependency>  

2、使用Demo

public class OrderEntity {  
    private int id;  
    private String name;  
    // Getters and setters are omitted  
}  

public class OrderDto {  
    private int id;  
    private String name;  
    // Getters and setters are omitted  
}  

public class PropWithDiffType {  
    private Integer id;  
    private String name;  
    // Getters and setters are omitted  
}  

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

2.1属性名称、类型都相同: 

@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.2属性名称相同、类型不同:

@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等),或反之:都不会被拷贝。 

2.3源类和目标类有相同的属性(两者的getter都存在),但目标类的setter不存在 

@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行 

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方法没有,所以报错  

3、小结: 

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

2. 当目标类的setter数目比getter少时,创建BeanCopier会失败而导致拷贝不成功。

三、自定义Converter转换器

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

源类和目标类: 

public class AccountEntity {  
    private int id;  
    private Timestamp createTime;  
    private BigDecimal balance;  
    // Getters and setters are omitted  
} 

public class AccountDto {  
    private int id;  
    private String name;  
    private String createTime;  
    private String balance;  
    // Getters and setters are omitted  
}  

1、不使用Converter 

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

package net.sf.cglib.core;  
  
public interface Converter {  
    // value 源对象属性,target 目标对象属性类,context 目标对象setter方法名  
    Object convert(Object value, Class target, Object context);  
}  

@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方法中要考虑所有的属性。

四、提供个工具类

package com.yusj.utils;  
  
import java.util.HashMap;  
import java.util.Map;  
  
import net.sf.cglib.beans.BeanCopier;  
  
/** 
 *  
 * 将beancopier做成静态类,方便拷贝 
 * <br>创建日期:2015年12月1日 
 * <br><b>Copyright 2015 UTOUU All Rights Reserved</b> 
 * @author yushaojian 
 * @since 1.0 
 * @version 1.0 
 */  
public class CglibBeanCopierUtils {  
      
    /** 
     *  
     */  
    public static Map<String, BeanCopier> beanCopierMap = new HashMap<String, BeanCopier>();  
      
    /**  
    * @Title: copyProperties  
    * @Description: TODO(bean属性转换)  
    * @param source 资源类 
    * @param target  目标类  
    * @author yushaojian 
    * @date 2015年11月25日下午4:56:44 
    */  
    public static void copyProperties(Object source,Object target){  
        String beanKey = generateKey(source.getClass(),target.getClass());  
        BeanCopier copier = null;  
        if (!beanCopierMap.containsKey(beanKey)) {  
            copier = BeanCopier.create(source.getClass(), target.getClass(), false);  
            beanCopierMap.put(beanKey, copier);  
        }else {  
            copier = beanCopierMap.get(beanKey);  
        }  
        copier.copy(source, target, null);  
    }  
    private static String generateKey(Class<?>class1,Class<?>class2){  
        return class1.toString() + class2.toString();  
    }  
    /*注: 
    (1)相同属性名,且类型不匹配时候的处理,ok,但是未满足的属性不拷贝; 
    (2)get和set方法不匹配的处理,创建拷贝的时候报错,无法拷贝任何属性(当且仅当sourceClass的get方法超过set方法时出现) 
    (3)BeanCopier  
    初始化例子:BeanCopier copier = BeanCopier.create(Source.class, Target.class, useConverter=true) 
    第三个参数userConverter,是否开启Convert,默认BeanCopier只会做同名,同类型属性的copier,否则就会报错. 
    copier = BeanCopier.create(source.getClass(), target.getClass(), false); 
    copier.copy(source, target, null); 
    (4)修复beanCopier对set方法强限制的约束 
    改写net.sf.cglib.beans.BeanCopier.Generator.generateClass(ClassVisitor)方法 
    将133行的 
    MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod()); 
    预先存一个names2放入 
     109        Map names2 = new HashMap(); 
     110        for (int i = 0; i < getters.length; ++i) { 
     111          names2.put(setters[i].getName(), getters[i]); 
                } 
    调用这行代码前判断查询下,如果没有改writeMethod则忽略掉该字段的操作,这样就可以避免异常的发生。*/  
  
}  

 

 

 

 

 参考文章:

https://blog.csdn.net/liangrui1988/article/details/41802275

https://ysj5125094.iteye.com/blog/2260885

http://cglib.sourceforge.net/apidocs/net/sf/cglib/beans/BeanCopier.html

转载于:https://www.cnblogs.com/java-jun-world2099/articles/11022357.html

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

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/197106.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

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