热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入解析CGLIBBeanCopier的应用与优化技巧

本文深入探讨了CGLIBBeanCopier在Bean对象复制中的应用及其优化技巧。相较于Spring的BeanUtils和Apache的BeanUtils,CGLIBBeanCopier在性能上具有显著优势。通过详细分析其内部机制和使用场景,本文提供了多种优化方法,帮助开发者在实际项目中更高效地利用这一工具。此外,文章还讨论了CGLIBBeanCopier在复杂对象结构和大规模数据处理中的表现,为读者提供了实用的参考和建议。


一、概述

  选择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 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 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


推荐阅读
  • JavaScript中属性节点的类型及应用
    本文深入探讨了JavaScript中属性节点的不同类型及其在实际开发中的应用,帮助开发者更好地理解和处理HTML元素的属性。通过具体的案例和代码示例,我们将详细解析如何操作这些属性节点。 ... [详细]
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • 使用Python在SAE上开发新浪微博应用的初步探索
    最近重新审视了新浪云平台(SAE)提供的服务,发现其已支持Python开发。本文将详细介绍如何利用Django框架构建一个简单的新浪微博应用,并分享开发过程中的关键步骤。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文介绍了如何通过 Maven 依赖引入 SQLiteJDBC 和 HikariCP 包,从而在 Java 应用中高效地连接和操作 SQLite 数据库。文章提供了详细的代码示例,并解释了每个步骤的实现细节。 ... [详细]
  • Scala 实现 UTF-8 编码属性文件读取与克隆
    本文介绍如何使用 Scala 以 UTF-8 编码方式读取属性文件,并实现属性文件的克隆功能。通过这种方式,可以确保配置文件在多线程环境下的一致性和高效性。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • XNA 3.0 游戏编程:从 XML 文件加载数据
    本文介绍如何在 XNA 3.0 游戏项目中从 XML 文件加载数据。我们将探讨如何将 XML 数据序列化为二进制文件,并通过内容管道加载到游戏中。此外,还会涉及自定义类型读取器和写入器的实现。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 图数据库中的知识表示与推理机制
    本文探讨了图数据库及其技术生态系统在知识表示和推理问题上的应用。通过理解图数据结构,尤其是属性图的特性,可以为复杂的数据关系提供高效且优雅的解决方案。我们将详细介绍属性图的基本概念、对象建模、概念建模以及自动推理的过程,并结合实际代码示例进行说明。 ... [详细]
author-avatar
百变精灵2596
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有