Orika JavaBean 映射工具使用
作者:京东零售 张宾
1.Orika 是什么?
Orika 是一个简单、快速的 JavaBean 拷贝框架,它能够递归地将数据从一个 JavaBean 复制到另一个 JavaBean,这在多层应用开发中是非常有用的。
2.为什么要引入 Orika?
工作中,我们经常需要将对象转换成不同的形式以适应不同的 api,或者在不同业务层中传输对象而不同分层的对象存在不同的格式,因此我们需要编写映射代码将对象中的属性值从一种类型转换成另一种类型。
Orika 用于简化多层之间的对象映射,避免苦苦挣扎于手工编码和基于反射的映射。Orika 关注尽可能地自动化,同时根据需要提供配置和扩展实现定制。
3.项目常用 javabean 映射技术
Java 反射:性能问题、无法解决嵌套 javabean 映射复制
手工映射:硬编码、代码量大
序列化方式:通过 json、xml 的序列化和反序列化方式,字段参数不相同时,需要硬编码赋值
4.常用 bean 映射工具
BeanUtils:apache 的 BeanUtils 和 spring 的 BeanUtils 中拷贝方法的原理都是先用 jdk 中 java.beans.Introspector 类的 getBeanInfo()方法获取对象的属性信息及属性 get/set 方法,接着使用反射(Method 的 invoke(Object obj, Object... args))方法进行赋值。apache 支持名称相同但类型不同的属性的转换,spring 支持忽略某些属性不进行映射,他们都设置了缓存保存已解析过的 BeanInfo 信息。
BeanCopier:cglib 的 BeanCopier 采用了不同的方法:它不是利用反射对属性进行赋值,而是直接使用 ASM 的 MethodVisitor 直接编写各属性的 get/set 方法(具体过程可见 BeanCopier 类的 generateClass(ClassVisitor v)方法)生成 class 文件,然后进行执行。由于是直接生成字节码执行,所以 BeanCopier 的性能较采用反射的 BeanUtils 有较大提高。
Orika:底层采用了 javassist 类库生成 Bean 映射的字节码,之后直接加载执行生成的字节码文件,因此在速度上比使用反射进行赋值会快很多。Orika 支持递归映射,将映射嵌套类直到用“简单”类型完成映射。它还包含故障保险,以正确处理正在尝试映射的对象中的递归引用。
5.如何使用 Orika?
5.1 maven 依赖
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.2</version>
</dependency>
5.2 两个 javabean 字段名称相同映射
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
//字段名相同映射
mapperFactory.classMap(Person.class, PersonInfo.class).byDefault().register();
MapperFacade mapperFacade = mapperFactory.getMapperFacade();
Person person = new Person();
person.setFirstName("张");
person.setLastName("三");
PersonInfo personInfo = mapperFacade.map(person, PersonInfo.class);
输出结果:
5.3 两个 javabean 字段名称不相同映射
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Person.class, PersonDesc.class)
.field("firstName", "givenName").field("lastName", "sirName")
.byDefault() .register();
MapperFacade mapperFacadeDesc = mapperFactory.getMapperFacade();
Person personNew = new Person();
personNew.setFirstName("王");
personNew.setLastName("五");
PersonDesc personDesc = mapperFacadeDesc.map(personNew, PersonDesc.class);
输出结果:
5.4 数组和 List 映射 javabean
public static class PersonNameList {
private List<String> nameList;
public PersonNameList(List<String> nameList) {
this.nameList = nameList;
}
//getter/setter 方法
}
mapperFactory.classMap(PersonNameList.class, Person.class)
.field("nameList[0]", "firstName")
.field("nameList[1]", "lastName").register();
List<String> nameParts = Arrays.asList(new String[]{"李", "四"});
Person person = mapperFactory.getMapperFacade().map(new PersonNameList(nameParts), Person.class);
输出结果:
5.5 相同类型 javabean 复制(原型模式)
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Person.class, Person.class).byDefault().register();
Person person = new Person();
person.setFirstName("张");
person.setLastName("三");
Person personNew = mapperFactory.getMapperFacade().map(person, Person.class);
输出结果:
5.6 嵌套 javabean 映射
mapperFactory.classMap(Flight.class, Flight.class).byDefault().register();
Cabin cabinY = new Cabin("Y", "8", 1200);
Cabin cabinF = new Cabin("C", "6", 1900);
Cabin cabinC = new Cabin("F", "A", 2400);
Flight flight = new Flight("CA1831", Lists.newArrayList(cabinY, cabinF, cabinC));
Flight flightCopy = mapperFactory.getMapperFacade().map(flight, Flight.class);
System.out.println("原对象:flight=" + JSON.toJSONString(flight));
System.out.println("新对象:flightCopy=" + JSON.toJSONString(flightCopy));
flightCopy.setFlightNo("MU5183");
flightCopy.getCabins().remove(0);
System.out.println("修改航变号和删除舱位:flightCopy=" + JSON.toJSONString(flightCopy)); System.out.println("原对象:flight=" + JSON.toJSONString(flight));
输出结果:
5.7 List 类型映射
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Cabin.class, CabinDesc.class).byDefault().register();
Cabin cabinY = new Cabin("Y", "8", 1200);
Cabin cabinF = new Cabin("C", "6", 1900);
Cabin cabinC = new Cabin("F", "A", 2400);
List<Cabin> cabinList = Lists.newArrayList(cabinY, cabinF, cabinC);
List<CabinDesc> cabinDescList = mapperFactory.getMapperFacade().mapAsList(cabinList, CabinDesc.class);
System.out.println("cabinList = " + JSON.toJSONString(cabinList));
System.out.println("cabinDescList = " + JSON.toJSONString(cabinDescList));
输出结果
5.8 自定义数据类型转化器(String to Date)
继承接口 CustomConverter 类,覆盖实现 convert 方法
public static class StringToDateConverter extends CustomConverter<String, Date> {
private String dateFormat;
public StringToDateConverter(String dateFormat) {
this.dateFormat = dateFormat;
}
public Date convert(String s, Type<? extends Date> type, MappingContext mappingContext) {
try {
return DateUtils.parseDate(s, dateFormat);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.getConverterFactory()
.registerConverter(new StringToDateConverter("yyyy-MM-dd"));
mapperFactory.classMap(ArrayMapper.PersonNameList.class, Person.class)
.field("nameList[0]", "firstName").field("nameList[1]", "lastName")
.field("nameList[2]", "birthDate").register();
List<String> nameParts = Arrays.asList(new String[]{"李", "四", "1989-10-11"});
Person person = mapperFactory.getMapperFacade().map(new ArrayMapper.PersonNameList(nameParts), Person.class);
System.out.println("nameParts = " + JSON.toJSONString(nameParts));
System.out.println("person = " + JSON.toJSONStringWithDateFormat(person,"yyyy-MM-dd"));
输出结果:
6. 各映射工具的性能测试
每种方法先预热执行 20 次,而后再执行 100000 次获取每次执行的平均时间
7.参考文献
官网:http://orika-mapper.github.io/orika-docs/intro.html
其他:https://www.jianshu.com/p/5daf68dc5758
http://tech.dianwoda.com/2017/11/04/gao-xing-neng-te
-xing-feng-fu-de-beanying-she-gong-ju-orika/
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/7afe0247a1e56881e7eb9006f】。文章转载请联系作者。
评论