背景

在分层的代码架构中,层与层之间的对象避免不了要做很多转换、赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils、BeanCopier、Dozer、Orika等等,本文将讲述上面几个工具的使用、性能对比及原理分析。

性能分析

其实这几个工具要做的事情很简单,而且在使用上也是类似的,所以我觉得先给大家看看性能分析的对比结果,让大家有一个大概的认识。我是使用JMH来做性能分析的,代码如下:

要复制的对象比较简单,包含了一些基本类型;有一次warmup,因为一些工具是需要“预编译”和做缓存的,这样做对比才会比较客观;分别复制1000、10000、100000个对象,这是比较常用数量级了吧。

@BenchmarkMode(Mode.AverageTime)  @OutputTimeUnit(TimeUnit.MICROSECONDS)  @Fork(1)  @Warmup(iterations =1)  @State(Scope.Benchmark)publicclassBeanMapperBenchmark{@Param({"1000","10000","100000"})privateinttimes;privateinttime;privatestaticMapperFactory mapperFactory;privatestaticMapper mapper;static{          mapperFactory =newDefaultMapperFactory.Builder().build();          mapperFactory.classMap(SourceVO.class, TargetVO.class)                  .byDefault()                  .register();            mapper = DozerBeanMapperBuilder.create()                  .withMappingBuilder(newBeanMappingBuilder() {                      @Overrideprotectedvoidconfigure() {                          mapping(SourceVO.class, TargetVO.class)                                  .fields("fullName","name")                                  .exclude("in");                      }                  }).build();      }publicstaticvoidmain(String[] args)throws Exception{          Options options =newOptionsBuilder()                  .include(BeanMapperBenchmark.class.getName()).measurementIterations(3)                  .build();newRunner(options).run();      }        @Setuppublicvoidprepare(){this.time = times;      }        @BenchmarkpublicvoidspringBeanUtilTest(){          SourceVO sourceVO = getSourceVO();for(inti =0; i

在我macbook下运行后的结果如下:

图片

Score表示的是平均运行时间,单位是微秒。从执行效率来看,可以看出 beanCopier > orika > springBeanUtil > dozer > apacheBeanUtil。这样的结果跟它们各自的实现原理有很大的关系,

下面将详细每个工具的使用及实现原理。

Spring的BeanUtils

使用

这个工具可能是大家日常使用最多的,因为是Spring自带的,使用也简单:BeanUtils.copyProperties(sourceVO, targetVO);

原理

Spring BeanUtils的实现原理也比较简答,就是通过Java的Introspector获取到两个类的PropertyDescriptor,对比两个属性具有相同的名字和类型,如果是,则进行赋值(通过ReadMethod获取值,通过WriteMethod赋值),否则忽略。

为了提高性能Spring对BeanInfo和PropertyDescriptor进行了缓存。

(源码基于:org.springframework:spring-beans:4.3.9.RELEASE)

/**    * Copy the property values of the given source bean into the given target bean.    *

Note: The source and target classes do not have to match or even be derived    * from each other, as long as the properties match. Any bean properties that the    * source bean exposes but the target bean does not will silently be ignored.    *@param source the source bean    *@param target the target bean    *@param editable the class (or interface) to restrict property setting to    *@param ignoreProperties array of property names to ignore    *@throws BeansException if the copying failed    *@see BeanWrapper    */privatestaticvoidcopyProperties(Objectsource,Objecttarget, Class editable,String... ignoreProperties)     throws BeansException {      Assert.notNull(source,"Source must not be null");    Assert.notNull(target,"Target must not be null");      Class actualEditable = target.getClass();if(editable !=null) {if(!editable.isInstance(target)) {thrownewIllegalArgumentException("Target class ["+ target.getClass().getName() +"] not assignable to Editable class ["+ editable.getName() +"]");     }     actualEditable = editable;    }//获取target类的属性(有缓存)  PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);    List ignoreList = (ignoreProperties !=null? Arrays.asList(ignoreProperties) :null);for(PropertyDescriptor targetPd : targetPds) {     Method writeMethod = targetPd.getWriteMethod();if(writeMethod !=null&& (ignoreList ==null|| !ignoreList.contains(targetPd.getName()))) {//获取source类的属性(有缓存)  PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());if(sourcePd !=null) {       Method readMethod = sourcePd.getReadMethod();if(readMethod !=null&&//判断target的setter方法入参和source的getter方法返回类型是否一致  ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {try{if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {          readMethod.setAccessible(true);         }//获取源值  Objectvalue = readMethod.invoke(source);if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {          writeMethod.setAccessible(true);         }//赋值到target  writeMethod.invoke(target, value);        }catch(Throwable ex) {thrownewFatalBeanException("Could not copy property '"+ targetPd.getName() +"' from source to target", ex);        }       }      }     }    }   }

小结

Spring BeanUtils的实现就是这么简洁,这也是它性能比较高的原因。

不过,过于简洁就失去了灵活性和可扩展性了,Spring BeanUtils的使用限制也比较明显,要求类属性的名字和类型一致,这点在使用时要注意。

Apache的BeanUtils

使用

Apache的BeanUtils和Spring的BeanUtils的使用是一样的:

BeanUtils.copyProperties(targetVO,sourceVO);

要注意,source和target的入参位置不同。

原理

Apache的BeanUtils的实现原理跟Spring的BeanUtils一样,也是主要通过Java的Introspector机制获取到类的属性来进行赋值操作,对BeanInfo和PropertyDescriptor同样有缓存,但是Apache BeanUtils加了一些不那么使用的特性(包括支持Map类型、支持自定义的DynaBean类型、支持属性名的表达式等等)在里面,使得性能相对Spring的BeanUtils来说有所下降。

(源码基于:commons-beanutils:commons-beanutils:1.9.3)

publicvoidcopyProperties(finalObjectdest, finalObjectorig)          throws IllegalAccessException, InvocationTargetException {if(dest ==null) {thrownewIllegalArgumentException                      ("No destination bean specified");          }if(orig ==null) {thrownewIllegalArgumentException("No origin bean specified");          }if(log.isDebugEnabled()) {              log.debug("BeanUtils.copyProperties("+ dest +", "+                        orig +")");          }// Apache Common自定义的DynaBean  if(originstanceofDynaBean) {              final DynaProperty[] origDescriptors =                  ((DynaBean) orig).getDynaClass().getDynaProperties();for(DynaProperty origDescriptor : origDescriptors) {                  finalStringname = origDescriptor.getName();// Need to check isReadable() for WrapDynaBean  // (see Jira issue# BEANUTILS-61)  if(getPropertyUtils().isReadable(orig, name) &&                      getPropertyUtils().isWriteable(dest, name)) {                      finalObjectvalue = ((DynaBean) orig).get(name);                      copyProperty(dest, name, value);                  }              }// Map类型  }elseif(originstanceofMap) {              @SuppressWarnings("unchecked")              final// Map properties are always of type   Map propMap = (Map) orig;for(finalMap.Entry entry : propMap.entrySet()) {                  finalStringname = entry.getKey();if(getPropertyUtils().isWriteable(dest, name)) {                      copyProperty(dest, name, entry.getValue());                  }              }// 标准的JavaBean  }else{              final PropertyDescriptor[] origDescriptors =//获取PropertyDescriptor  getPropertyUtils().getPropertyDescriptors(orig);for(PropertyDescriptor origDescriptor : origDescriptors) {                  finalStringname = origDescriptor.getName();if("class".equals(name)) {continue;// No point in trying to set an object's class  }//是否可读和可写  if(getPropertyUtils().isReadable(orig, name) &&                      getPropertyUtils().isWriteable(dest, name)) {try{//获取源值  finalObjectvalue =                              getPropertyUtils().getSimpleProperty(orig, name);//赋值操作  copyProperty(dest, name, value);                      }catch(final NoSuchMethodException e) {// Should not happen  }                  }              }          }        }

小结

Apache BeanUtils的实现跟Spring BeanUtils总体上类似,但是性能却低很多,这个可以从上面性能比较看出来。阿里的Java规范是不建议使用的。

BeanCopier

使用

BeanCopier在cglib包里,它的使用也比较简单:

@TestpublicvoidbeanCopierSimpleTest(){          SourceVO sourceVO = getSourceVO();          log.info("source={}", GsonUtil.toJson(sourceVO));          TargetVO targetVO =newTargetVO();          BeanCopier bc = BeanCopier.create(SourceVO.class,TargetVO.class,false);          bc.copy(sourceVO, targetVO,null);          log.info("target={}", GsonUtil.toJson(targetVO));      }

只需要预先定义好要转换的source类和target类就好了,可以选择是否使用Converter,这个下面会说到。

在上面的性能测试中,BeanCopier是所有中表现最好的,那么我们分析一下它的实现原理。

原理

BeanCopier的实现原理跟BeanUtils截然不同,它不是利用反射对属性进行赋值,而是直接使用cglib来生成带有的get/set方法的class类,然后执行。由于是直接生成字节码执行,所以BeanCopier的性能接近手写

get/set。

BeanCopier.create方法

publicstaticBeanCopiercreate(Class source, Class target,booleanuseConverter){          Generator gen =newGenerator();          gen.setSource(source);          gen.setTarget(target);          gen.setUseConverter(useConverter);returngen.create();      }publicBeanCopiercreate(){              Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);return(BeanCopier)super.create(key);          }

这里的意思是用KEY_FACTORY创建一个BeanCopier出来,然后调用create方法来生成字节码。

KEY_FACTORY其实就是用cglib通过BeanCopierKey接口生成出来的一个类

privatestaticfinalBeanCopierKey KEY_FACTORY =        (BeanCopierKey)KeyFactory.create(BeanCopierKey.class);interfaceBeanCopierKey{publicObjectnewInstance(String source, String target,booleanuseConverter);      }

通过设置

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "path");

可以让cglib输出生成类的class文件,我们可以反复译看看里面的代码

下面是KEY_FACTORY的类

publicclassBeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fdextendsKeyFactoryimplementsBeanCopierKey{privatefinalString FIELD_0;privatefinalString FIELD_1;privatefinalbooleanFIELD_2;publicBeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd() {      }publicObjectnewInstance(String var1, String var2,booleanvar3){returnnewBeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(var1, var2, var3);      }publicBeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(String var1, String var2,booleanvar3) {this.FIELD_0 = var1;this.FIELD_1 = var2;this.FIELD_2 = var3;      }//省去hashCode等方法。。。  }

继续跟踪Generator.create方法,由于Generator是继承AbstractClassGenerator,这个AbstractClassGenerator是cglib用来生成字节码的一个模板类,Generator的super.create其实调用

AbstractClassGenerator的create方法,最终会调用到Generator的模板方法generateClass方法,我们不去细究AbstractClassGenerator的细节,重点看generateClass。

这个是一个生成java类的方法,理解起来就好像我们平时写代码一样。

publicvoidgenerateClass(ClassVisitor v) {              Type sourceType = Type.getType(source);              Type targetType = Type.getType(target);              ClassEmitter ce = new ClassEmitter(v);//开始“写”类,这里有修饰符、类名、父类等信息  ce.begin_class(Constants.V1_2,                             Constants.ACC_PUBLIC,                             getClassName(),                             BEAN_COPIER,                             null,                             Constants.SOURCE_FILE);//没有构造方法  EmitUtils.null_constructor(ce);//开始“写”一个方法,方法名是copy  CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);//通过Introspector获取source类和target类的PropertyDescriptor  PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);              PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);                            Map names = new HashMap();for(inti =0; i

即使没有使用过cglib也能读懂生成代码的流程吧,我们看看没有使用useConverter的情况下生成的代码:

publicclassObject$$BeanCopierByCGLIB$$d1d970c8extendsBeanCopier {publicObject$$BeanCopierByCGLIB$$d1d970c8() {      }publicvoidcopy(Objectvar1,Objectvar2, Converter var3) {          TargetVO var10000 = (TargetVO)var2;          SourceVO var10001 = (SourceVO)var1;          var10000.setDate1(((SourceVO)var1).getDate1());          var10000.setIn(var10001.getIn());          var10000.setListData(var10001.getListData());          var10000.setMapData(var10001.getMapData());          var10000.setP1(var10001.getP1());          var10000.setP2(var10001.getP2());          var10000.setP3(var10001.getP3());          var10000.setPattr1(var10001.getPattr1());      }  }

在对比上面生成代码的代码是不是豁然开朗了。

再看看使用useConverter的情况:

public class Object$$BeanCopierByCGLIB$$d1d970c7 extends BeanCopier {      private static final Class CGLIB$load_class$java$2Eutil$2EDate;      private static final Class CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner;      private static final Class CGLIB$load_class$java$2Eutil$2EList;      private static final Class CGLIB$load_class$java$2Eutil$2EMap;      private static final Class CGLIB$load_class$java$2Elang$2EInteger;      private static final Class CGLIB$load_class$java$2Elang$2ELong;      private static final Class CGLIB$load_class$java$2Elang$2EByte;      private static final Class CGLIB$load_class$java$2Elang$2EString;        public Object$$BeanCopierByCGLIB$$d1d970c7() {      }        public void copy(Object var1, Object var2, Converter var3) {          TargetVO var4 = (TargetVO)var2;          SourceVO var5 = (SourceVO)var1;          var4.setDate1((Date)var3.convert(var5.getDate1(), CGLIB$load_class$java$2Eutil$2EDate,"setDate1"));          var4.setIn((Inner)var3.convert(var5.getIn(), CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner,"setIn"));          var4.setListData((List)var3.convert(var5.getListData(), CGLIB$load_class$java$2Eutil$2EList,"setListData"));          var4.setMapData((Map)var3.convert(var5.getMapData(), CGLIB$load_class$java$2Eutil$2EMap,"setMapData"));          var4.setP1((Integer)var3.convert(var5.getP1(), CGLIB$load_class$java$2Elang$2EInteger,"setP1"));          var4.setP2((Long)var3.convert(var5.getP2(), CGLIB$load_class$java$2Elang$2ELong,"setP2"));          var4.setP3((Byte)var3.convert(var5.getP3(), CGLIB$load_class$java$2Elang$2EByte,"setP3"));          var4.setPattr1((String)var3.convert(var5.getPattr1(), CGLIB$load_class$java$2Elang$2EString,"setPattr1"));          var4.setSeq((Long)var3.convert(var5.getSeq(), CGLIB$load_class$java$2Elang$2ELong,"setSeq"));      }        static void CGLIB$STATICHOOK1() {          CGLIB$load_class$java$2Eutil$2EDate = Class.forName("java.util.Date");          CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner = Class.forName("beanmapper_compare.vo.SourceVO$Inner");          CGLIB$load_class$java$2Eutil$2EList = Class.forName("java.util.List");          CGLIB$load_class$java$2Eutil$2EMap = Class.forName("java.util.Map");          CGLIB$load_class$java$2Elang$2EInteger = Class.forName("java.lang.Integer");          CGLIB$load_class$java$2Elang$2ELong = Class.forName("java.lang.Long");          CGLIB$load_class$java$2Elang$2EByte = Class.forName("java.lang.Byte");          CGLIB$load_class$java$2Elang$2EString = Class.forName("java.lang.String");      }        static {          CGLIB$STATICHOOK1();      }  }

小结

BeanCopier性能确实很高,但从源码可以看出BeanCopier只会拷贝名称和类型都相同的属性,而且如果一旦使用Converter,BeanCopier只使用Converter定义的规则去拷贝属性,所以在convert方法中要考虑所有的属性。

Dozer

使用

上面提到的BeanUtils和BeanCopier都是功能比较简单的,需要属性名称一样,甚至类型也要一样。但是在大多数情况下这个要求就相对苛刻了,要知道有些VO由于各种原因不能修改,有些是外部接口SDK的对象,

有些对象的命名规则不同,例如有驼峰型的,有下划线的等等,各种什么情况都有。所以我们更加需要的是更加灵活丰富的功能,甚至可以做到定制化的转换。

Dozer就提供了这些功能,有支持同名隐式映射,支持基本类型互相转换,支持显示指定映射关系,支持exclude字段,支持递归匹配映射,支持深度匹配,支持Date to String的date-formate,支持自定义转换Converter,支持一次mapping定义多处使用,支持EventListener事件监听等等。不仅如此,Dozer在使用方式上,除了支持API,还支持XML和注解,满足大家的喜好。更多的功能可以参考这里

由于其功能很丰富,不可能每个都演示,这里只是给个大概认识,更详细的功能,或者XML和注解的配置,请看官方文档。

privateMapper dozerMapper;        @Beforepublicvoidsetup(){          dozerMapper = DozerBeanMapperBuilder.create()                  .withMappingBuilder(newBeanMappingBuilder() {                      @Overrideprotectedvoidconfigure() {                          mapping(SourceVO.class, TargetVO.class)                                  .fields("fullName","name")                                  .exclude("in");                      }                  })                  .withCustomConverter(null)                  .withEventListener(null)                  .build();      }            @TestpublicvoiddozerTest(){          SourceVO sourceVO = getSourceVO();log.info("sourceVO={}", GsonUtil.toJson(sourceVO));          TargetVOmap= dozerMapper.map(sourceVO, TargetVO.class);log.info("map={}", GsonUtil.toJson(map));      }

java signature 性能_Java常见bean mapper的性能及原理分析相关推荐

  1. Java常见bean mapper的性能及原理分析

    来源:http://r6d.cn/VxXn 背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanU ...

  2. java监控数据库性能_Java:GraalVM数据库流性能

    java监控数据库性能 GraalVM是JVM块的新成员. 它是一个开源虚拟机,能够同时运行多种编程语言,例如Java,Rust和JavaScript. GraalVM还具有一个新的内部代码优化器管道 ...

  3. 干货!MySQL常见的面试题+索引原理分析!

    今天给大家分享一篇干货,面试必备之Mysql索引底层原理分析,文章末尾有福利哟!!!! Mysql索引的本质 Mysql索引的底层原理 Mysql索引的实战经验 面试 问:数据库中最常见的慢查询优化方 ...

  4. java cpu 监控工具_Java自带的GUI性能监控工具Jconsole以及JisualVM简介

    1 Jconsole 1.1 简介以及连接 JConsole是一个基于JMX的GUI工具,用于连接正在运行的JVM,它是Java自带的简单性能监控工具.下面以对tomcat的监控为例,带领大家熟悉Jc ...

  5. java百度文库_java 常见异常 (百度文库)

    1. java.lang.nullpointerexception 这个异常大家肯定都经常遇到,异常的解释是"程序遇上了空指针",简单地说就是调用了未经初始化的对象或者是不存在的对 ...

  6. java获得电脑性能_Java:使用SingletonStream获得性能

    java获得电脑性能 仅具有一个元素的Java流有时会在应用程序中造成不必要的开销. 了解如何使用SingletonStream对象并为其中某些流获得十倍的性能,并了解如何同时简化代码. 背景 Jav ...

  7. java arraylist范围_Java常见集合之ArrayList深入分析

    1 /* 2 继承自AbstractList,实现了List.RandomAccess.Cloneable.Serializable接口3 1)RandomAccess接口:用来快速随机存取,在实现了 ...

  8. java关键字吗_JAVA常见关键字

    JAVA的关键字都是小写的 在JAVA中目前一共有53个关键字:其中由51+2个保留字=53个关键字 1.JAVA的保留关键字(2个) const--常量,常数:用于修改字段或局部变量的声明. got ...

  9. java entryset循环_Java之Map遍历方式性能分析:ketSet 与 entrySet

    keySet():将Map中所有的键存入到Set集合中.因为set具备迭代器,所以可以以迭代方式取出所有的键,再根据get方法获取每一个键对应的值,其仅能通过get()取key. entrySet() ...

最新文章

  1. memset函数详细说明
  2. asp.net在线压缩和解压缩的实现
  3. C语言使用1到9求出所有k个数字的所有组合的算法(附完整源码)
  4. 2019年1月份GitHub上最热门的Java开源项目
  5. CIO感悟:IT人转型之“势、道、术”
  6. Modelsim的下载及安装
  7. 百货商场数字化会员营销 购物中心私域流量运营系统
  8. PB通过VDN实现Http上传、下载
  9. Dorado7之AjaxAction
  10. SQL语句的增删改查(详细)
  11. Unity 移动的几种方法(从某一点移动到另外一点)
  12. sap系统和服务器的关系,erp系统和sap系统的区别
  13. AutoCAD坐标与图像坐标
  14. 【蓝桥杯】:奇怪的分式
  15. 在PC下载微信视频号里面的视频
  16. ECMWF ERA-interim数据下载——手动下载
  17. java 主线程与子线程的执行顺序
  18. Qt windows下获取CPU、主板、硬盘、网卡等相关信息
  19. 路漫漫其修远兮,吾将上下而求索——小酌重构系列[0]开篇有益
  20. 什么是宏任务与微任务?

热门文章

  1. LeetCode简单题之移动零
  2. SSM框架整合教程(2020最新版)
  3. Xilinx FPGA全局介绍
  4. deeplearning搜索空间
  5. 机器学习PAL数据预处理
  6. Visual SLAM
  7. 深度学习模型训练过程
  8. 【杂】LaTeX中一些符号的输入方法
  9. [JAVAEE] 使用Postman测试接口
  10. RxJava 解除订阅---------Disposable.dispose()方法