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

BeanCopier基本用法

public class User {
private int age;
private String name; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}
public class UserDto {
private int age;
private String name; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}
public class UserWithDiffType {
private Integer age;
private String name; public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

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

@Test
public void normalCopyTest() {
// create(Class source, Class target, boolean useConverter)
final BeanCopier beanCopier = BeanCopier.create(User.class, UserDto.class, false);
User user = new User();
user.setAge(10);
user.setName("zhangsan");
UserDto userDto = new UserDto();
beanCopier.copy(user, userDto, null);
Assert.assertEquals(10, userDto.getAge());
Assert.assertEquals("zhangsan", userDto.getName());
}

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

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

@Test
public void normalCopyTest() {
// create(Class source, Class target, boolean useConverter)
final BeanCopier beanCopier = BeanCopier.create(User.class, UserWithDiffType.class, false);
User user = new User();
user.setAge(10);
user.setName("zhangsan");
UserWithDiffType userDto = new UserWithDiffType();
beanCopier.copy(user, userDto, null);
Assert.assertEquals(null, userDto.getAge());
Assert.assertEquals("zhangsan", userDto.getName());
}

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

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

自定义转换器

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

public class Account {
private int id;
private Date createTime;
private BigDecimal balance; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public Date getCreateTime() {
return createTime;
} public void setCreateTime(Date createTime) {
this.createTime = createTime;
} public BigDecimal getBalance() {
return balance;
} public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
public class AccountDto {
private int id;
private String createTime;
private String balance; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getCreateTime() {
return createTime;
} public void setCreateTime(String createTime) {
this.createTime = createTime;
} public String getBalance() {
return balance;
} public void setBalance(String balance) {
this.balance = balance;
}
}

1. 不使用Converter

@Test
public void noConverterTest() {
Account po = new Account();
po.setId(1);
po.setCreateTime(new Date());
po.setBalance(BigDecimal.valueOf(4000L));
BeanCopier copier = BeanCopier.create(Account.class, AccountDto.class, false);
AccountDto dto = new AccountDto();
copier.copy(po, dto, null);
// 类型不同,未拷贝
Assert.assertNull(dto.getBalance());
// 类型不同,未拷贝
Assert.assertNull(dto.getCreateTime());
}

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

public class TestCase {

    @Test
public void noConverterTest() {
Account po = new Account();
po.setId(1);
po.setCreateTime(new Date());
po.setBalance(BigDecimal.valueOf(4000L));
BeanCopier copier = BeanCopier.create(Account.class, AccountDto.class, true);
AccountDto dto = new AccountDto();
AccountConverter converter = new AccountConverter();
copier.copy(po, dto, converter);
// 类型不同,未拷贝
Assert.assertEquals("4000", dto.getBalance());
// 类型不同,未拷贝
Assert.assertEquals("2018-12-13", dto.getCreateTime());
}
} class AccountConverter implements Converter { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); @SuppressWarnings("rawtypes")
@Override
public Object convert(Object source, Class target, Object context) {
if (source instanceof Integer) {
return (Integer) source;
} else if (source instanceof Date) {
Date date = (Date) source;
return sdf.format(date);
} else if (source instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) source;
return bd.toPlainString();
}
return null;
}
}

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

封装BeanCopier

@Test
public void costTest() {
List<User> list1 = new ArrayList<>(100);
for (int i = 0; i < 100; i++) {
User po = new User();
po.setId(1);
po.setCreateTime(new Date());
po.setBalance(BigDecimal.valueOf(4000L));
list1.add(po);
}
BeanCopier copier = BeanCopier.create(User.class, UserDto.class, false);
long start = System.currentTimeMillis();
List<UserDto> list2 = new ArrayList<>(100);
for (User user : list1) {
UserDto dto = new UserDto();
//BeanUtils.beanCopy(user, dto);
copier.copy(user, dto, null);
list2.add(dto);
}
System.out.printf("took time: %d(ms)%n",System.currentTimeMillis() - start);
}

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

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

依赖:

<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>reflectasm</artifactId>
<version>1.11.7</version>
</dependency>
</dependencies>

封装工具

public class WrapperBeanCopier {

    private WrapperBeanCopier() {
//do nothing
} private static final Map<String, BeanCopier> BEAN_COPIER_CACHE = new ConcurrentHashMap<>(); private static final Map<String, ConstructorAccess> CONSTRUCTOR_ACCESS_CACHE = new ConcurrentHashMap<>(); public static void copyProperties(Object source, Object target) {
BeanCopier copier = getBeanCopier(source.getClass(), target.getClass());
copier.copy(source, target, null);
} private static BeanCopier getBeanCopier(Class sourceClass, Class targetClass) {
String beanKey = generateKey(sourceClass, targetClass);
BeanCopier copier = null;
if (!BEAN_COPIER_CACHE.containsKey(beanKey)) {
copier = BeanCopier.create(sourceClass, targetClass, false);
BEAN_COPIER_CACHE.put(beanKey, copier);
} else {
copier = BEAN_COPIER_CACHE.get(beanKey);
}
return copier;
} /**
* 两个类的全限定名拼接起来构成Key
*
* @param sourceClass
* @param targetClass
* @return
*/
private static String generateKey(Class<?> sourceClass, Class<?> targetClass) {
return sourceClass.getName() + targetClass.getName();
} public static <T> T copyProperties(Object source, Class<T> targetClass) {
T t = null;
try {
t = targetClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
}
copyProperties(source, t);
return t;
} public static <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClass) {
if (sourceList == null || sourceList.isEmpty()) {
return Collections.emptyList();
}
ConstructorAccess<T> constructorAccess = getConstructorAccess(targetClass);
List<T> resultList = new ArrayList<>(sourceList.size());
for (Object o : sourceList) {
T t = null;
try {
t = constructorAccess.newInstance();
copyProperties(o, t);
resultList.add(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return resultList;
} private static <T> ConstructorAccess<T> getConstructorAccess(Class<T> targetClass) {
ConstructorAccess<T> constructorAccess = CONSTRUCTOR_ACCESS_CACHE.get(targetClass.getName());
if (constructorAccess != null) {
return constructorAccess;
}
try {
constructorAccess = ConstructorAccess.get(targetClass);
constructorAccess.newInstance();
CONSTRUCTOR_ACCESS_CACHE.put(targetClass.toString(), constructorAccess);
} catch (Exception e) {
throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
}
return constructorAccess;
} }

使用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. Collection接口的常用方法

    [添加功能] boolean add(Object obj); //添加一个元素. boolean addAll(Collection c); //添加一个集合的元素(在后面追加). [删除功能] v ...

  2. C/C++中如何获取数组的长度(宏&模板)

    C.C++中没有提供 直接获取数组长度的函数.对于存放字符串的字符数组提供了一个strlen函数获取长度,那么对于其他类型的数组如何获取他们的长度呢?其中一种方法是使 用  sizeof(array) ...

  3. detailFormatter bootstrapTable

    detailView : true,//会导致表格最开头多出一列 detailFormatter :function(index, row, element){ var image = '<di ...

  4. python基础篇_001_初识Python

    一.Python环境 windows环境安装Python步骤 .下载安装包:https://www.python.org/downloads/windows/  .安装:默认安装路径:C:\pytho ...

  5. TF:TF下CNN实现mnist数据集预测 96%采用placeholder用法+2层C及其max_pool法+隐藏层dropout法+输出层softmax法+目标函数cross_entropy法+AdamOptimizer算法

    import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data # number 1 to 10 ...

  6. Booksort POJ - 3460 (IDA*)

    Description The Leiden University Library has millions of books. When a student wants to borrow a ce ...

  7. POJ 2352 Stars【树状数组】

    <题目链接> 题目大意: 题目给出n个点,这些点按照y坐标的升序,若y相同,则按照x的升序顺序输入,问,在这些点中,左下角的点的数量分别在0~n-1的点分别有多少个,写出它们的对应点数. ...

  8. Python并发复习2 - 多线程模块threading

    一.多线程的调用 threading 模块建立在thread 模块之上.thread模块以低级.原始的方式来处理和控制线程,而threading 模块通过对thread进行二次封装, 提供了更方便的a ...

  9. XamarinSQLite教程在Xamarin.iOS项目中定位数据库文件

    XamarinSQLite教程在Xamarin.iOS项目中定位数据库文件 开发者可以在指定的路径中找到复制的数据库文件,具体的操作步骤如下: (1)单击Mac电脑中Finder菜单中的“前往”|“前 ...

  10. angular笔记_3

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...