前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >打开orika的正确方式

打开orika的正确方式

作者头像
kirito-moe
发布于 2018-04-27 03:50:07
发布于 2018-04-27 03:50:07
3.9K00
代码可运行
举报
运行总次数:0
代码可运行

缘起

架构分层

开发分布式的项目时,DO持久化对象和DTO传输对象的转换是不可避免的。集中式项目中,DO-DAO-SERVICE-WEB的分层再寻常不过,但分布式架构(或微服务架构)需要拆分模块时,不得不思考一个问题:WEB层能不能出现DAO或者DO对象?我给出的答案是否定的。

这张图曾出现在我过去的文章中,其强调了一个分层的要素:服务层(应用层)和表现层应当解耦,后者不应当触碰到任何持久化对象,其所有的数据来源,均应当由前者提供。

DTO的位置

就系统的某一个模块,可以大致分成领域层model,接口定义层api,接口实现层/服务层service,表现层web。

  • service 依赖 model + api
  • web 依赖 api

在我们系统构建初期,DTO对象被想当然的丢到了model层,这导致web对model产生了依赖;而在后期,为了满足前面的架构分层,最终将DTO对象移动到了api层(没有单独做一层)

没有DTO时的痛点

激发出DTO这样一个新的分层其实还有两个原因。

其一,便是我们再也不能忍受在RPC调用时JPA/hibernate懒加载这一特性带来的坑点。如果试图在消费端获取服务端传来的一个懒加载持久化对象,那么很抱歉,下意识就会发现这行不通,懒加载技术本质是使用字节码技术完成对象的代理,然而代理对象无法天然地远程传输,这与你的协议(RPC or HTTP)无关。

其二,远程调用需要额外注意网络传输的开销,如果生产者方从数据库加载出了一个一对多的依赖,而消费者只需要一这个实体的某个属性,多的实体会使得性能产生下降,并没有很好的方式对其进行控制(忽略手动set)。可能有更多痛点,由此可见,共享持久层,缺少DTO层时,我们的系统灵活性和性能都受到了制约。

从DTO到Orika

各类博客不乏对DTO的讨论,对领域驱动的理解,但却鲜有文章介绍,如何完成DO对象到DTO对象的转换。我们期待有一款高性能的,易用的工具来帮助我们完成实体类的转换。便引出了今天的主角:Orika。

Orika是什么?

Orika是一个简单、快速的JavaBean拷贝框架,它能够递归地将数据从一个JavaBean复制到另一个JavaBean,这在多层应用开发中是非常有用的。

Orika的竞品

相信大多数朋友接触过apache的BeanUtils,直到认识了spring的BeanUtils,前者被后者完爆,后来又出现了Dozer,Orika等重量级的Bean拷贝工具,在性能和特性上都有了很大的提升。

先给结论,众多Bean拷贝工具中,今天介绍的Orika具有想当大的优势。口说无凭,可参考下面文章中的各个工具的对比:http://tech.dianwoda.com/2017/11/04/gao-xing-neng-te-xing-feng-fu-de-beanying-she-gong-ju-orika/?utmsource=tuicool&utmmedium=referral

简单整理后,如下所示:

  • BeanUtils

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

  • BeanCopier

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

  • Dozer

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

  • Orika

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

Orika入门

引入依赖

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>${orika.version}</version>
</dependency>

基础概念

  • MapperFactory
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

MapperFactory用于注册字段映射,配置转换器,自定义映射器等,而我们关注的主要是字段映射这个特性,在下面的小节中会介绍。

  • MapperFacade
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonSource source = new PersonSource();
PersonDest destination = mapper.map(source, PersonDest.class);

MapperFacade和spring,apache中的BeanUtils具有相同的地位,负责对象间的映射,也是实际使用中,我们使用的最多的类。

至于转换器,自定义映射器等等概念,属于Orika的高级特性,也是Orika为什么被称作一个重量级框架的原因,引入Orika的初衷是为了高性能,易用的拷贝对象,引入它们会给系统带来一定的侵入性,所以本文暂不介绍,详细的介绍,可参考官方文档:http://orika-mapper.github.io/orika-docs/intro.html

映射字段名完全相同的对象

如果DO对象和DTO对象的命名遵守一定的规范,那无疑会减少我们很大的工作量。那么,规范是怎么样的呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Person {
  private String name;
  private int age;
  private Date birthDate;
  List<Address> addresses; // <1>
  // getters/setters omitted
}
class PersonDto { 
  private String name;
  private int age;
  private Date birthDate;
  List<AddressDto> addresses; // <1>
  // getters/setters omitted
}
class Address {
    private String name;
}
class AddressDto {
    private String name;
}

基本字段类型自不用说,关键是打上<1>标签的地方,按照通常的习惯, List<AddressDto>变量名会被命名为addressDtos,但我更加推荐与DO对象统一命名,命名为addresses。这样Orika在映射时便可以自动映射两者。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
Person person = new Person();
//一顿赋值
PersonDto personDto = mapperFactory.getMapperFacade().map(person, PersonDto.class);

这样便完成了两个对象之间的拷贝,你可能会思考:需要我们指定两个类的映射关系吗?集合可以自动映射吗?这一切Orika都帮助我们完成了,在默认行为下,只要类的字段名相同,Orika便会尽自己最大的努力帮助我们映射。

映射字段名不一致的对象

我对于DTO的理解是:DTO应当尽可能与DO的字段保持一致,不增不减不改,但可能出于一些特殊原因,需要映射两个名称不同的字段,Orika当然也支持这样常见的需求。只需要在MapperFactory中事先注册便可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Person {
  private String id;
  private Name name;
  private List<Name> knownAliases;
  private Date birthDate;
}

public class Name {
  private String first;
  private String last; 
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class PersonDto {
  private String personId;
  private String firstName;
  private String lastName;
  private Date birthDate;
  private String[][] aliases;
}

完成上述两个结构不甚相似的对象时,则需要我们额外做一些工作,剩下的便和之前一致了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
factory.classMap(Person.class, PersonDto.class) // <2>
      .field("id","personId")
      .field("name.first", "firstName")
      .field("name.last", "lastName")
      .field("knownAliases{first}", "aliases{[0]}")
      .field("knownAliases{last}", "aliases{[1]}")
      .byDefault() //<1>
      .register();

这些 .{}[]这些略微有点复杂的表达式不需要被掌握,只是想表明:如果你有这样需求,Orika也能支持。上述连续点的行为被称为 fluent-style ,这再不少框架中有体现。

<1> 注意byDefault()这个方法,在指定了classMap行为之后,相同字段互相映射这样的默认行为需要调用一次这个方法,才能被继承。

<2> classMap()方法返回了一个ClassMapBuilder对象,如上所示,我们见识到了它的field(),byDefault(),register()方法,这个建造者指定了对象映射的众多行为,还包括几个其他有用的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
classMapBuilder.field("a","b");//Person和PersonDto的双向映射
classMapBuilder.fieldAToB("a","b");//单向映射  
classMapBuilder.fieldBToA("a","b");//单向映射
classMapBuilder.exclude("a");//移除指定的字段映射,即使字段名相同也不会拷贝
classMapBuilder.field("a","b").mapNulls(false).mapNullsInReverse(false);//是否拷贝空属性,默认是true

更多的API可以参见源码

集合映射

在类中我们之前已经见识过了List

与List的映射。如果根对象就是一个集合,List 映射为 List也是很常见的需求,这也很方便:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();List<Person> persons = new ArrayList<>();List<PersonDto> personDtos = mapperFactory.getMapperFacade().mapAsList(persons, PersonDto.class);

递归映射

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class A {  private B b;}class B {    private C c;}class C {    private D d;}class D {    private String name;}

Orika默认支持递归映射。

泛型映射

对泛型的支持是Orika的另一强大功能,这点在文档中只是被提及,网上并没有找到任何一个例子,所以在此我想稍微着重的介绍一下。既然文档没有相关的介绍,那如何了解Orika是怎样支持泛型映射的呢?只能翻出Orika的源码,在其丰富的测试用例中,可以窥见其对各种泛型特性的支持:https://github.com/orika-mapper/orika/tree/master/tests/src/main/java/ma/glasnost/orika/test/generics

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Response<T> {    private T data;}public class ResponseDto<T> {    private T data;}

当出现泛型时,按照前面的思路去拷贝,看看结果会如何,泛型示例1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Testpublic void genericTest1(){    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();    Response<String> response = new Response<>();    response.setData("test generic");    ResponseDto<String> responseDto = mapperFactory.getMapperFacade().map(response, ResponseDto.class);// *    Assert.assertFalse("test generic".equals(responseDto.getData()));}

会发现responseDto并不会Copy成功吗,特别是在*处,你会发现无所适从,没办法把ResponseDto传递进去 ,同样的,还有下面的泛型示例2

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Testpublic void genericTest2(){    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();    Response<Person> response = new Response<>();    Person person = new Person();    person.setName("test generic");    response.setData(person);    Response<PersonDto> responseDto = mapperFactory.getMapperFacade().map(response, Response.class);    Assert.assertFalse(responseDto.getData() instanceof PersonDto);}

Response中的String和PersonDto在运行时(Runtime)泛型擦除这一特性难住了不少人,那么,Orika如何解决泛型映射呢?

我们可以发现MapperFacade的具有一系列的重载方法,对各种类型的泛型拷贝进行支持

可以看到几乎每个方法都传入了一个Type,用于获取拷贝类的真实类型,而不是传入.class字节码,下面介绍正确的打开姿势:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Testpublic void genericTest1() {    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();    Response<String> response = new Response<>();    response.setData("test generic");    Type<Response<String>> fromType = new TypeBuilder<Response<String>>() {}.build();    Type<ResponseDto<String>> toType = new TypeBuilder<ResponseDto<String>>() {}.build();    ResponseDto<String> responseDto = mapperFactory.getMapperFacade().map(response, fromType, toType);    Assert.assertTrue("test generic".equals(responseDto.getData()));}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Testpublic void genericTest2() {    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();    Response<Person> response = new Response<>();    Person person = new Person();    person.setName("test generic");    response.setData(person);    Type<Response<Person>> fromType = new TypeBuilder<Response<Person>>() {}.build();    Type<Response<PersonDto>> toType = new TypeBuilder<Response<PersonDto>>() {}.build();    Response<PersonDto> responseDto = mapperFactory.getMapperFacade().map(response, fromType, toType);    Assert.assertEquals("test generic" , responseDto.getData().getName());}

浅拷贝or深拷贝

虽然不值得一提,但职业敏感度还是催使我们想要测试一下,Orika是深拷贝还是浅拷贝,毕竟浅拷贝有时候会出现一些意想不到的坑点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Testpublic void deepCloneTest() throws Exception {    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();    Person person = new Person();    Address address = new Address();    person.setAddress(address);    PersonDto personDto = mapperFactory.getMapperFacade().map(person, PersonDto.class);    Assert.assertFalse(personDto.getAddress().hashCode() == person.getAddress().hashCode());}

结论:在使用Orika时可以放心,其实现的是深拷贝,不用担心原始类和克隆类指向同一个对象的问题。

更多的特性?

你如果关心Orika是否能完成你某项特殊的需求,在这里可能会对你有所帮助:http://orika-mapper.github.io/orika-docs/faq.html

怎么样,你是不是还在使用BeanUtils呢?尝试一下Orika吧!

---------------------------分割线 ----------------------------

公司的活动,下面的连接帮忙点击进入一下即可。

http://www.itaiping.com/concurrency/lottery/zfAnniShared.action?sopenid=oBsGSwteYv91YR8b2Tmem3YjGt2s&suserId=922654295688482816&from=timeline&isappinstalled=0&code=081vmREb0Ji8at1MNSFb0OvJEb0vmREe&state=123

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kirito的技术分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
JavaScript——ES6模块化与异步编程高级用法
大家都遵守同样的模块化规范写代码,降低了沟通程表,极大方便了各个模块之间的相互调用,利人利己。
岳泽以
2022/10/26
7510
JavaScript——ES6模块化与异步编程高级用法
ES6的异步编程之Generator
异步编程对 JavaScript 语言太重要。JavaScript 只有一根线程,如果没有异步编程,根本没法用,非卡死不可。
javascript.shop
2019/09/04
5310
ES6的异步编程之Generator
Node.js异步编程(下)
如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?
Qwe7
2022/04/23
7780
ES6新特性
由于ES6在一些低版本的浏览器上无法运行,需转成ES5之前的版本兼容,以下有几种方案可以自动转换
jinghong
2020/05/09
1K0
ES6新特性
Promise用法详解(一)
注意:上面new出来promise,只代表形式上的一个异步操作。就是说,我们只知道他是一个 异步操作,但做什么具体异步事情目前还不清楚。
全栈程序员站长
2022/07/04
3820
Promise用法详解(一)
nodejs(三)
必须在 package.json 的根节点中添加 "type": "module" 节点
且陶陶
2023/04/12
4520
nodejs(三)
【JS】236-JS 异步编程六种方案(原创)
我们知道Javascript语言的执行环境是"单线程"。也就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。
pingan8787
2019/07/25
9860
【JS】236-JS 异步编程六种方案(原创)
《深入浅出Node.js》:Node异步编程解决方案 之 ES6 Promise
在上一篇讲了异步编程解决方案之一的事件发布-订阅模式,使用事件模式时,执行流程需要被预先设定。即便是分支,也需要预先设定,这是由发布-订阅模式的运行机制决定的。这个方法的灵活性比较受限,那是否有一种先执行异步调用,延迟传递处理的方式呢?在ES6发布之前,解决方案是Promise/Deferred模式,现在则推荐ES6官方提供的Promise。
前端_AWhile
2019/08/29
9280
async关键字
在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
Qwe7
2022/04/23
2750
异步发展流程-手摸手带你实现一个Promise
首先介绍一下高阶函数,即一个函数的参数是函数或者函数返回值为函数,此函数称做高阶函数。
Careteen
2022/02/14
9660
ES6的异步编程之async
异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题。
javascript.shop
2019/09/04
4850
ES6的异步编程之async
ES6 Promise详解之缝合红宝书ES6标准入门
去年暑假的时候就学了Promise,最近在学node的时候又遇到了再复习一遍,写个博客缝合一波阮佬的ES6标准入门与红宝书中的内容,用我自己的白话给大家讲一下。
henu_Newxc03
2021/12/26
6590
Node.js异步编程
1. 同步API,异步API //路径拼接 const public = path.join(_ dirname, 'public') ; //通过返回值拿到 //请求地址解析 const url0bj = url. parse (req. ur1) ; //通过返回值拿到 //读取文件 fs. readFile(' ./demo.txt', 'utf8', (err, result) => { console .1og (result) ; }) ; //通过函数方式拿到 同步API:只有当前
清出于兰
2020/10/29
2K0
Node.js异步编程
day044:JS异步编程有哪些方案?为什么会出现这些方案?
关于 JS 单线程、EventLoop 以及异步 I/O 这些底层的特性,我们之前做过了详细的拆解,不在赘述。在探究了底层机制之后,我们还需要对代码的组织方式有所理解,这是离我们最日常开发最接近的部分,异步代码的组织方式直接决定了开发和维护的效率,其重要性也不可小觑。尽管底层机制没变,但异步代码的组织方式却随着 ES 标准的发展,一步步发生了巨大的变革。接着让我们来一探究竟吧!
用户3806669
2021/03/10
6770
从零开始写一个符合Promises/A+规范的promise
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。本篇不注重讲解promise的用法,关于用法,可以看阮一峰老师的ECMAScript 6系列里面的Promise部分:
桃翁
2019/10/23
1.1K0
从零开始写一个符合Promises/A+规范的promise
记两道关于事件循环的题
这里的关键其实是搞清楚 await async2() 做了什么事情。我以为在 async1 内部,async2 被调用之后,就会继续往后执行,因此是先打印 async1 end ,再回到主栈打印 start。然而 async2 里面包含了一个异步操作,在异步操作得到结果之前,其实是会跳出当前 async1 函数的执行栈,优先去执行同步任务的,所以这里其实会先执行 start,再去执行 async1 end。具体地说:
Chor
2020/05/18
4100
Promise解决回调嵌套问题及终极解决方案async 和 await
目的: promise是书写异步代码的另一种方式, 解决回调函数嵌套的问题 1.如何创建一个 promise 对象
青梅煮码
2023/03/02
2.4K0
js异步编程的三种模式
很容易可以看出,上述代码会依次输出1,2。因为代码是从上到下,依次执行,执行完f1(),才会执行f2()。但是如果f1()中的代码执行的是读取文件或者ajax操作呢,文件的读取都需要一定时间,难道我们需要完全等到文件完全读完再进行写操作么?为了解决这个问题,接下来我们来探究一下js中 同步和异步 的概念。
hellocoder2029
2022/09/28
8660
Node理论笔记:异步编程
在JavaScript中,函数是一等公民,使用非常自由,无论是调用它,或者作为参数,或者作为返回值均可。
Ashen
2020/06/01
1.1K0
JavaScript异步编程
平时开发经常会用到js异步编程,由于前端展示页面都是基于网络机顶盒(IPTV的一般性能不太好,OTT较好),目前公司主要采取的异步编程的方式有setTimeout、setInterval、requestAnimationFrame、ajax,为什么会用到异步呢,就拿业务来说,若前端全部采取同步的方式,那加载图片、生成dom、网络数据请求都会大大增加页面渲染时长。
Jack Chen
2018/09/14
9290
JavaScript异步编程
相关推荐
JavaScript——ES6模块化与异步编程高级用法
更多 >
LV.0
Web前端开发
目录
  • 缘起
    • 架构分层
    • DTO的位置
    • 没有DTO时的痛点
  • 从DTO到Orika
    • Orika是什么?
    • Orika的竞品
  • Orika入门
    • 引入依赖
    • 基础概念
    • 映射字段名完全相同的对象
    • 映射字段名不一致的对象
    • 集合映射
    • 递归映射
    • 泛型映射
    • 浅拷贝or深拷贝
    • 更多的特性?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档