一、概述
选择Cglib的BeanCopier进行Bean拷贝的理由是,其性能要比Spring的BeanUtils,Apache的BeanUtils和PropertyUtils要好很多,尤其是数据量比较大的情况下。
之前的一篇文章:Easy-mapper教程——模型转换工具 提到了Cglib的BeanCopier使用ASM字节码生成技术,所以性能会非常好。
下面的文章内容直接整理自网上资源,有错误之处敬请谅解,后续再整理。
二、相关使用案例
1、引入maven依赖
<dependency>
<groupId>asmgroupId>
<artifactId>asmartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>asmgroupId>
<artifactId>asm-commonsartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>asmgroupId>
<artifactId>asm-utilartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>cglibgroupId>
<artifactId>cglib-nodepartifactId>
<version>2.2.2version>
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
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做成静态类,方便拷贝
*
创建日期:2015年12月1日
*
Copyright 2015 UTOUU All Rights Reserved
* @author yushaojian
* @since 1.0
* @version 1.0
*/
public class CglibBeanCopierUtils {
/**
*
*/
public static Map
/**
* @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
}
调用这行代码前判断查询下,如果没有改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