cglib BeanCopier的使用

cglib BeanCopier的使用

一、概述

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

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

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

二、相关使用案例

1、引入maven依赖

asm

asm

3.3.1

asm

asm-commons

3.3.1

asm

asm-util

3.3.1

cglib

cglib-nodep

2.2.2

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做成静态类,方便拷贝

*
创建日期:2015年12月1日

*
Copyright 2015 UTOUU All Rights Reserved

* @author yushaojian

* @since 1.0

* @version 1.0

*/

public class CglibBeanCopierUtils {

/**

*

*/

public static Map beanCopierMap = new HashMap();

/**

* @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(Classclass1,Classclass2){

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

相关推荐

英雄联盟卡牌大师厉害吗值得练吗(英雄联盟卡牌大师皮肤)
ppm轉換器
beat365体育官网

ppm轉換器

📅 10-21 👁️ 1879
科普视频
365体育平台网址

科普视频

📅 10-02 👁️ 3657