在Java开发中,经常会有一个需求,将一个 Bean 复制到另外一个 Bean,尤其是在后台分层的场景下,在不同的层之间传递信息,经常需要进行 这样的一个对象复制工作,类似于:

val source: PersonSource = ...val dest = new PersonDest()

dest.setName( source.getName )
dest.setEmail( source.getEmail )
dest.setAddress( source.getAddress )
...

因为这样的代码过于冗长,大量的这样的代码,大大的提升了代码的复杂性,不仅工作无趣,而且很容易遗漏,代码可阅读性差。万能的程序员 自然就会发明一个又一个的轮子来解决这个问题:

  • apache BeanUtils

  • dozer:http://dozer.sourceforge.net

  • orika:http://orika-mapper.github.io/

所有这样的轮子,都包括这样的一些特性:

  • 字段映射。 一般通过同名字段映射,或者使用 annotation 定义Bean间的字段映射。

  • 类型映射。 诸如完成 String -> Int 这样的类型转换。

  • 嵌套映射。

BeanUtils是一个最简单的,通过reflection来实现对象间的映射,这样存在的问题是有很大的反射开销,对性能要求很高的场景是不适合的。 orika等后续的框架则是通过字节码生成等技术,来实现性能的提升。

当我们走向函数式编程时,我们也会面临着对象的映射和复制的问题,BeanUtils/Orika等框架一般是基于Java Beans模型,而在函数式编程 中,我们更倾向于使用 immutable 的对象,beanutils/orika等模型存在不匹配的情况,于是,我们构造了一款适合于函数式编程的对象复制 方式,即 BeanBuilder.

这是一个基本的使用方法:

val source = PersonSource( name = "wangzx", email = "wangzx@qq.com", address="here" )
val dest = BeanBuilder.build[PersonDest](source)()

上面的代码中,我们会返回一个PersonDest对象(也是一个不可变的Case Class),并完成对象属性的复制工作,一个完整的使用方式如下:

  def build[T](sources: Any*)(additions: (String, Any)*) : T 
  • T 目标类型,必须是一个Immutable Case Class,BeanBuilder 设计上是一个从 Case Class -> Case Class的转换工具

  • sources 数据来源。 可以是0到多个对象。 这些对象的字段 会成为我们目标对象的字段 的来源。与BeanUtils、Orika等不的是, 我们的目标对象可以从多个源对象中复制属性。

  • additions 以 "name" -> value 的形式定义的附加属性设置,意思是将 value 复制到 目标对象的 name 属性。

BeanBuilder 在设计上也不同于 BeanUtils,Orika,BeanBuilder首先被设计成为精确的、类型安全的对象复制,体现在:

  • 精确。BeanBuilder 目前限定是同名复制,而且必须没有歧义。即值或者通过 additions 精确赋值,或者唯一存在于 sources 对象之中,即只有一个 source 对象有这个同名属性。

  • 所有目标对象中的非缺省值,都必须有数据来源,不能缺失(不会自动使用null作为目标值)。

  • 类型安全。 BeanBuilder不会自动的进行 String -> Int 的转换,除非你主动的容许这个类型转换。当然,在BeanBuilder中,我们 可以更简单的定义源和目标的类型转换能力,并且在一种类型安全的方式进行。

BeanBuilder的类型转换采用如何策略,这里,F是源类型(值为f),T是目标类型(值为t):

  • 如果 F 是 T的相同,或者子类型, 则总是成功的。t = f

  • 如果存在一个隐式转换,则总是成功的。t = implicit_convert(f)

  • 如果 存在 t = f.copyTo[T] 是类型安全的,那么,会使用这个转换。 t = f.copyTo[T]

  • 如果 FT是容器类型 X[A] 和 X[B],并且 A 和 B 可以满足上述的转换关系,则 f = t.map ( A -> B )

  • 如果 F 和 T都是Case Class,并且可以递归使用  t = BeanBuilder.build[T](f)()

如果单纯根据如上的规则,可能很多的同学会觉得,这么复杂的一套规则,在Java中要么是完全做不到的(大部分),要么是这会产生巨大的 运行时性能损耗,会存在严重的性能问题。那么,最最神奇的事情发生了:

BeanBuilde会是同类框架中性能最优的,它能够达到数据复制的理论最快速度,在没有任何性能损耗的情况下,实现类型安全的对象复制

为什么作者可以这么自信的说呢?这就是Macro的神奇,与BeanUtils/Orika等不同的是, BeanBuilder会在编译期间,完成所有的类型 检查工作,完成源对象与目标对象之间的映射关系检查和建立,为你自动的编写出Copy代码,就像手工编写的转换代码代码一样。你或者可以 理解为 BeanBuilder 是一个扩展了的 scalac 编译特性,当然这一切建立在 scala 的标准语言特性之上。

经过前面系列的铺垫,相信读者已经具备了看懂这个代码的基础,BeanBuilder的全部源代码也很简单,约200行,具体内容就不在本文中 详细陈述了,读者如有兴趣,可以查看:https://github.com/wangzaixiang/scala-sql/blob/master/src/main/scala/wangzx/scala_commons/sql/BeanBuilder.scala

通过 BeanBuilder 这个案列,相信读者能够感知到一个完全不同的思维模式,打开新的想象空间。而这,就是 元编程 的魅力之所在吧。

剧透

  • dapeng 2.0 即将全新发布,新版本将是对1.0版本的完全重构,对简化代码结构的情况下,提供了全新的异步支持、Scala服务开发支持。 我们的主力工程师 @EmuuGrass(美女)、@jackliang 后续会准备不少的技术分享。

  • dapeng 2.0 将内置一个极速json解析器,dapeng-soa将在支持thrift binary/compact binary的基础之上,提供极速的JSON服务。 等待@ever大神的新博文分享。

参考:

神奇的Scala Macro之旅(三)- 实际应用

转自:

神奇的Scala Macro之旅(4):BeanBuilder

神奇的Scala Macro之旅(四)- BeanBuilder的更多相关文章

  1. 神奇的Scala Macro之旅(三)- 实际应用

    在上一篇中,我们示范了使用macro来重写 Log 的 debug/info 方法,并大致的介绍了 macro 的基本语法.基本使用方法.以及macro背后的一些概念, 如AST等.那么,本篇中,我们 ...

  2. 神奇的Scala Macro之旅(二)- 一个实例

    优化的日志方式 package macros_demo import scala.language.experimental.macrosimport org.slf4j._import scala. ...

  3. 神奇的Scala Macro之旅(一)- 什么时候用宏

    在Lisp语言中,macro是一个神器,可以“动态的生成代码”,然后被执行,这种方式给到Lisp无限的表达能力.除Lisp之外,很少有语言支持Macro这个特性,我记得 GWT之中曾经有一个类似的Ge ...

  4. Scala函数式编程(四)函数式的数据结构 下

    前情提要 Scala函数式编程指南(一) 函数式思想介绍 scala函数式编程(二) scala基础语法介绍 Scala函数式编程(三) scala集合和函数 Scala函数式编程(四)函数式的数据结 ...

  5. Scala快速入门(四)——继承、接口

    Scala快速入门(四)--继承.接口 一.继承 1.继承的概念:省略 2.模板: class Person(n:String,a:Int) { var name:String=n var age:I ...

  6. Hexo之旅(四):文章编写技巧

    hexo 编写文章可以使用以下命令创建hexo new "文件名" #创建的文章会在_pots目录下文章的后缀名是以md命名的文件格式,遵循markdown语法,所以编写文章可以使 ...

  7. scala函数定义的四种方式

    最近开始接触scala编程语言,觉得还是比较新的一个东西,虽然说和java比较像,是java的继承者,兼顾面向对象编程和函数式编程的优点,但是,终究是一个新的东西,还是要从最基本的学起.而这当中,函数 ...

  8. 神奇的Bank系统之旅哦

        奋斗不能等待,我们不能等到垂暮之年再去“全力以赴”.让我们从现在开始,为理想而努力,为人生而拼搏.精诚所至,金石为开,相信奋斗会让我们的青春之花绽放得更加绚烂,让我们的人生之路走下来不留遗憾. ...

  9. Scala入门学习笔记四--List使用

    前言 本篇将介绍一个和Array很相似的集合List,更多内容请参考:Scala教程 本篇知识点概括 List的构造 List与Array的区别 List的常用方法 List伴生对象的方法 ::和:: ...

随机推荐

  1. ffmpeg转换参数和压缩输出大小的比率 参考 最新版本FFMPEG

    https://blog.cnlabs.NET/3668.html ffmpeg 转换压缩比例 FFMPEG如果是压缩为FLV文件 3个编码可选1. -c:v flv 标准FLV编码 这个好处是速度快 ...

  2. goquery 添加header 发起请求

    goquery 添加header 发起请求 我们知道使用net/http 很容易发起GET or POST 请求:并且在发起http请求时候,可以很容易的对header进行干预 例如: client ...

  3. SpringMVC配置多个数据源

    多数据源,说白了,就是多数据库. 想要实现多数据库查询,只需简单四步即可实现! 第一步: 配置 jdbc.properties: # MySQL #========================== ...

  4. java-将评论内容过滤特殊表情emoj符号,保存到mysql中

    正常操作评论,保存时,若评论内容含有特殊表情符号,后台将报错如下: Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8mb4_ ...

  5. 强大的代码编辑器 phpstorm version 2016.2 License Server激活

    "磨刀不误砍柴工","工欲善其事必先利其器",找个一个好的代码开发编辑工具可以让我们事半功倍,并且代码质量得到保障,在这里就推荐一款强大的代码编辑器,不对其实可 ...

  6. Hession集成Spring + maven依赖通讯comm项目 + 解决@ResponseBody中文乱码

    hessian结合spring的demo         hessian的maven依赖: <!-- hessian --> <dependency>         < ...

  7. Mac下MySQL无my-default.cnf

    转自https://www.jianshu.com/p/628bcf8bb557 As of MySQL 5.7.18, my-default.ini is no longer included in ...

  8. Servlet线程

    一,servlet容器如何同时处理多个请求. Servlet采用多线程来处理多个请求同时访问,Servelet容器维护了一个线程池来服务请求.线程池实际上是等待执行代码的一组线程叫做工作者线程(Wor ...

  9. File,FileInfo,FileStream,StreamReader的区别与用法

    概括的说,File,FileInfo,FileStream是用于文件 I/O 的类,StreamReader是用于从流读取和写入流的类,使用之前都需using System.IO. 先定义一个TXT文 ...

  10. javascript 用函数语句和表达式定义函数的区别详解

    通常我们会看到以下两种定义函数的方式: // 函数语句 function fn(str) { console.log(str); }; // 表达式定义 var fnx=function(str) { ...