1. 概述

日常Java开发项目中,我们经常需要将对象转换成其他形式的对象,因此我们需要编写映射代码将对象中的属性值从一种类型转换成另一种类型。

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

apache的BeanUtils

spring的BeanUtils

cglib的BeanCopier

2.比较

2.1 BeanUtils

BeanUtils一套开发包,Apache公司提供 ,专门进行javabean操作,在web层各种框架中被使用,例如:struts 使用BeanUtils操作JavaBean 。

实例
1、下载BeanUtils的jar :commons-beanutils 、commons-logging,需要同时下载两个jar包。(BeanUtils依赖Logging的jar包 )
2、将beanutils和logging的 jar包复制 工程/WebContent/WEB-INF/lib

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

commons.beanutils-1.8.3.jar

spring.beans-4.2.3.RELEASE.jar

<dependency>
  <groupId>commons-beanutils</groupId>
  <artifactId>commons-beanutils</artifactId>
  <version>1.9.3</version>
</dependency>

import org.apache.commons.beanutils.BeanUtils;

public class TestBeanUtils extends HttpServlet {
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //1.设置编码
      req.setCharacterEncoding("utf-8");
     //2.获取数据
      Map<String, String[]> params = req.getParameterMap();
     //System.out.println(params);
      //3.使用BeanUtils工具类封装User对象
       Users user = new Users();
try {
      BeanUtils.populate(user, params);
} catch (IllegalAccessException e) {
     e.printStackTrace();
} catch (InvocationTargetException e) {
     e.printStackTrace();
}
System.out.println(user);

}

@Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
           this.doPost(req, resp);
     }
}

2.2 BeanCopier

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

2.3 Dozer

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

2.4 Orika

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

3. 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();

自定义转换器

orika同样支持自定义转换器,将指定类型或指定名称的属性做映射时添加自定义操作,例如,将String类型的或某个属性映射后加一个前缀,或者将Integer类型映射后加1等:

public class MyConverter extends CustomConverter<Date,MyDate> {
public MyDate convert(Date source, Type<? extends MyDate> destinationType) {
// return a new instance of destinationType with all properties filled
//example:source + 1;
}
}

Date为源类型中要做转换的属性数据类型,例如StringInteger等,MyDate为目标类型中要做转换的属性数据类型。

如果需要定义全局范围的转换:

ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter(new MyConverter());

如果仅需要某几个属性使用转换器:

ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter("myConverterIdValue", new MyConverter()); mapperFactory.classMap( Source.class, Destination.class )
.fieldMap("sourceField1", "sourceField2").converter("myConverterIdValue").add()
...
.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感到不放心,实际应用前可以写个测试类查看它的转换结果是否符合预期。

Java bean常见映射工具分析和比较的更多相关文章

  1. java Bean的映射工具

    数据层,业务逻辑层和表现层,每一层对应的应该是不一样的对象,所以就有个这么个java bean的映射工具 dozer.......................................... ...

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

    原地址:http://tech.dianwoda.com/2017/11/04/gao-xing-neng-te-xing-feng-fu-de-beanying-she-gong-ju-orika/ ...

  3. Dozer JAVA的POJO 映射工具

    Dozerhttp://www.manongjc.com/article/50949.html JAVA的映射工具 BeanUtils dozer的使用方法https://blog.csdn.net/ ...

  4. java bean属性拷贝工具类比对(性能+功能)

    业务系统中经常需要两个对象进行属性的拷贝,不能否认逐个的对象拷贝是最快速最安全的做法,但是当数据对象的属性字段数量超过程序员的容忍的程度比如通常超过5个属性的时候,代码因此变得臃肿不堪,使用一些方便的 ...

  5. Bean映射工具之Apache BeanUtils VS Spring BeanUtils

    背景 在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息,比如DTO数据传输对象和数据对象DO,我们需要将DO对象进 ...

  6. Java实体映射工具MapStruct的使用

    官网地址:http://mapstruct.org/ MapStruct 是一个代码生成器,简化了不同的 Java Bean 之间映射的处理,所谓的映射指的就是从一个实体变化成一个实体.例如我们在实际 ...

  7. 5种常见Bean映射工具的性能比对

    本文由 JavaGuide 翻译自 https://www.baeldung.com/java-performance-mapping-frameworks .转载请注明原文地址以及翻译作者. 1. ...

  8. java bean 转换工具

    考量要素: 1.简单的约定优于配置的同名属性copy 2.嵌套属性copy 3.flattern(扁平化)支持,要支持N层结构的copy到一层结构. 4.性能 如下这个网页,里面提到了好多工具. ht ...

  9. 几种常见的JavaScript混淆和反混淆工具分析实战

    几种常见的JavaScript混淆和反混淆工具分析实战 xiaix2016-03-05+8共1195751人围观 ,发现 5 个不明物体WEB安全 信息安全常被描述成一场军备竞赛,白帽与黑帽,渗透测试 ...

随机推荐

  1. Flutter 中那么多组件,难道要都学一遍?

    在 Flutter 中一切皆是 组件,仅仅 Widget 的子类和间接子类就有 350 多个,整理的 Flutter组件继承关系图 可以帮助大家更好的理解学习 Flutter,回归正题,如此多的组件到 ...

  2. C# 9.0 新特性之 Lambda 弃元参数

    阅读本文大概需要不到 1 分钟. 弃元(Discards) 是在 C# 7.0 的时候开始支持的,它是一种人为丢弃不使用的临时虚拟变量.语法上它是用来赋值的,但它却不被分配存储空间,即没有值,所以不能 ...

  3. linux之文件基本操作

    文件/目录管理命令: cd命令主要是改变目录的功能 cd ~ 返回登录目录 cd / 返回系统根目录 cd ../ 或者cd ..  返回上一级目录 cd -  返回上一次访问的目录 pwd命令用于显 ...

  4. 常用API - Arrays、Math、Object

    Arrays类 概述 此类包含用来操作数组(比如排序和搜索)的各种方法.此类还包含一个允许将数组作为列表来查看的静态工厂. 除非特别注明,否则如果指定数组引用为 null,则此类中的方法都会抛出 Nu ...

  5. F5隐写工具使用

      0x00 前言 今天在实验吧看到一个图片隐写的题目,用了stegslove和winHex分析一通发现并没有什么有效信息.看了评论区大佬的提示说用到了F5隐写工具,所以百度教程用了一下,发现确实解决 ...

  6. CentOS7.7 安装并配置JDK 1.8

    本文介绍如何在CentOS中安装oracleJDK1.8并配置环境变量 1.下载并安装jdk1.8 进入下载页:https://www.oracle.com/technetwork/java/java ...

  7. shell进阶篇之数组应用案例

    数组中可以存放多个值. Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小. 与大部分编程语言类似,数组元素的下标由0开始. Shell 数组用括号来表示,元素用"空格 ...

  8. Mysql基础(十一):Self Join

    Summary: 如何使用 MySQL self join 进行表的 自己对自己的join操作.. 前面的教程,已经教过join语法,都是两个表的之间的操作,特殊的,当一个表自己和自己进行join,那 ...

  9. celery 基础教程(一):工作流程,架构以及概念

    1.工作流程 celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者)和worker(任务的处理者). clients发出消息到队列中,broker将队列中的 ...

  10. redis(二十三):Redis 集群(proxy 型)二

    redis的确是一个非常高效的缓存服务器,但是单台redis服务器的内存管理能力有限,如果一味的加大内存的话会导致redis服务器的性能下降,所以就必须要搭建redis集群来提供服务.在redis官方 ...