转载

常见Bean映射工具分析评测及Orika介绍

Bean映射工具选择

工作中,我们经常需要将对象转换成不同的形式以适应不同的api,或者在不同业务层中传输对象而不同分层的对象存在不同的格式,因此我们需要编写映射代码将对象中的属性值从一种类型转换成另一种类型。

进行这种转换除了手动编写大量的 get/set 代码,还可以使用一些方便的类库,常用的有apache的 BeanUtils ,spring的 BeanUtils ,cglib的 BeanCopier

BeanUtils

apache的 BeanUtils 和spring的 BeanUtils 中拷贝方法的原理都是先用jdk中 java.beans.Introspector 类的 getBeanInfo() 方法获取对象的属性信息及属性get/set方法,接着使用反射( Methodinvoke(Object obj, Object... args) )方法进行赋值。apache支持名称相同但类型不同的属性的转换,spring支持忽略某些属性不进行映射,他们都设置了缓存保存已解析过的 BeanInfo 信息。

BeanCopier

cglib的 BeanCopier 采用了不同的方法:它不是利用反射对属性进行赋值,而是直接使用ASM的 MethodVisitor 直接编写各属性的 get/set 方法(具体过程可见 BeanCopier 类的 generateClass(ClassVisitor v) 方法)生成class文件,然后进行执行。由于是直接生成字节码执行,所以 BeanCopier 的性能较采用反射的 BeanUtils 有较大提高,这一点可在后面的测试中看出。

Dozer

使用以上类库虽然可以不用手动编写 get/set 方法,但是他们都不能对不同名称的对象属性进行映射。在定制化的属性映射方面做得比较好的有 Dozer ,Dozer支持简单属性映射、复杂类型映射、双向映射、隐式映射以及递归映射。可使用xml或者注解进行映射的配置,支持自动类型转换,使用方便。但Dozer底层是使用reflect包下 Field 类的 set(Object obj, Object value) 方法进行属性赋值,执行速度上不是那么理想。

Orika

那么有没有特性丰富,速度又快的Bean映射工具呢,这就是下面要介绍的 Orika ,Orika是近期在github活跃的项目,底层采用了javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件,因此在速度上比使用反射进行赋值会快很多,下面详细介绍Orika的使用方法。

Orika使用

依赖

<dependency>  
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.2</version><!-- or latest version -->
</dependency>

简单映射

  1. 构造一个MapperFactory
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
  1. 注册字段映射
mapperFactory.classMap(PersonSource.class, PersonDestination.class)  
   .field("firstName", "givenName")
   .field("lastName", "sirName")
   .byDefault()
   .register();
  1. 进行映射
MapperFacade mapper = mapperFactory.getMapperFacade();

PersonSource source = new PersonSource();  
// set some field values
...
// map the fields of 'source' onto a new instance of PersonDest
PersonDest destination = mapper.map(source, PersonDest.class);

在第二步进行的字段映射是双向的,我们可以从目标类型映射回源类型, byDefault() 方法用于注册名称相同的属性(如果所有属性名称都相同则可以省略第2步),如果不希望某个字段参与映射,可以使用 exclude 方法

复杂映射

数组和List的映射

如果在目标类和目的类中分别有下面的属性

class BasicPerson {  
  private List<String> nameParts;
  // getters/setters omitted
}
class BasicPersonDto {  
  private String firstName;
  private String lastName;
  // getters/setters omitted
}

可以使用下面的方式进行映射:

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)  
   .field("nameParts[0]", "firstName")
   .field("nameParts[1]", "lastName")
   .register();

类类型的映射

class Name {  
   private String first;
   private String last;
   private String fullName;
   // getters/setters 
}

class BasicPerson {  
  private Name name;
  // getters/setters omitted
}
class BasicPersonDto {  
  private String firstName;
  // getters/setters omitted
}

使用:

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)  
   .field("name.first", "firstName")
   .register();

其他说明

  1. Orika支持递归映射,将映射嵌套类直到用“简单”类型完成映射。它还包含故障保险,以正确处理正在尝试映射的对象中的递归引用。

  2. 在于spring集成时,可以将MapperFactory设置为单例

各映射工具的性能测试

构造一个包含普通类型及类类型的Bean对象,使用jmh微基准框架进行测试。由于jvm会对热点代码进行优化:方法反射调用次数超过阈值时会生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码进行执行。

故测试时每种方法先预热执行15次,而后再执行100次获取每次执行的平均时间:

Benchmark                     Mode  Samples   Score  Score error  Units  
o.s.MyBenchmark.apache        avgt      100  25.246        0.535  us/op  
o.s.MyBenchmark.beanCopier    avgt      100   0.004        0.000  us/op  
o.s.MyBenchmark.byHand        avgt      100   0.004        0.000  us/op  
o.s.MyBenchmark.dozer         avgt      100   5.855        0.260  us/op  
o.s.MyBenchmark.orika         avgt      100   0.353        0.017  us/op  
o.s.MyBenchmark.spring        avgt      100   0.627        0.020  us/op

统计报告中Units单位为微秒/次,由Score项可以看出,基于ASM的cglib BeanCopier拷贝速度基本和手写get/set方法的速度无异,其次的就是基于javassist的Orika了,Orika的速度是spring BeanUtils的两倍,Dozer的20倍,Apache BeanUtils的120倍。

综上,当属性名和属性类型完全相同时使用BeanCopier是最好的选择,当存在属性名称不同或者属性名称相同但属性类型不同的情况时,使用Orika是一种不错的选择。如果你对Orika感到不放心,实际应用前可以写个测试类查看它的转换结果是否符合预期。

原文  http://tech.dianwoda.com/2017/11/04/gao-xing-neng-te-xing-feng-fu-de-beanying-she-gong-ju-orika/
正文到此结束
Loading...