神奇的Scala Macro之旅(四)- BeanBuilder
在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]如果
F和T是容器类型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的更多相关文章
- 神奇的Scala Macro之旅(三)- 实际应用
在上一篇中,我们示范了使用macro来重写 Log 的 debug/info 方法,并大致的介绍了 macro 的基本语法.基本使用方法.以及macro背后的一些概念, 如AST等.那么,本篇中,我们 ...
- 神奇的Scala Macro之旅(二)- 一个实例
优化的日志方式 package macros_demo import scala.language.experimental.macrosimport org.slf4j._import scala. ...
- 神奇的Scala Macro之旅(一)- 什么时候用宏
在Lisp语言中,macro是一个神器,可以“动态的生成代码”,然后被执行,这种方式给到Lisp无限的表达能力.除Lisp之外,很少有语言支持Macro这个特性,我记得 GWT之中曾经有一个类似的Ge ...
- Scala函数式编程(四)函数式的数据结构 下
前情提要 Scala函数式编程指南(一) 函数式思想介绍 scala函数式编程(二) scala基础语法介绍 Scala函数式编程(三) scala集合和函数 Scala函数式编程(四)函数式的数据结 ...
- Scala快速入门(四)——继承、接口
Scala快速入门(四)--继承.接口 一.继承 1.继承的概念:省略 2.模板: class Person(n:String,a:Int) { var name:String=n var age:I ...
- Hexo之旅(四):文章编写技巧
hexo 编写文章可以使用以下命令创建hexo new "文件名" #创建的文章会在_pots目录下文章的后缀名是以md命名的文件格式,遵循markdown语法,所以编写文章可以使 ...
- scala函数定义的四种方式
最近开始接触scala编程语言,觉得还是比较新的一个东西,虽然说和java比较像,是java的继承者,兼顾面向对象编程和函数式编程的优点,但是,终究是一个新的东西,还是要从最基本的学起.而这当中,函数 ...
- 神奇的Bank系统之旅哦
奋斗不能等待,我们不能等到垂暮之年再去“全力以赴”.让我们从现在开始,为理想而努力,为人生而拼搏.精诚所至,金石为开,相信奋斗会让我们的青春之花绽放得更加绚烂,让我们的人生之路走下来不留遗憾. ...
- Scala入门学习笔记四--List使用
前言 本篇将介绍一个和Array很相似的集合List,更多内容请参考:Scala教程 本篇知识点概括 List的构造 List与Array的区别 List的常用方法 List伴生对象的方法 ::和:: ...
随机推荐
- 得到Android keystore签名的命令方法
keytool -list -v -keystore keyfile.jks 上面这个命令是用来得到一些签名字符串,这些字符串用来在各种平台上填资料的 再来个手动签名的命令: jarsigner -v ...
- Scala编程入门---面向对象编程之对象
对象 Object,相当于class单个实例,通常在里面放一些静态的filed或method 第一次调用object方法时候,就会执行object的constructor,也就是Object中不在me ...
- 第三方支付设计——自有账户支付
笔者在上一篇blog<<第三方支付架构设计之-帐户体系>>中已经稍微全面的阐述了第三方支付架构设计中的账户体系,在该体系中,其实涉及了各种各样的账户:银行侧账户(包括用户在银行 ...
- LinkedList源码
1.介绍及注意事项 链表由Josh Bloch书写,属于Java集合框架中的一种,LinkedList实现的是双链表,实现了所有的链表操作,可能够实现所有元素(包括)的基本操作. 链表是非线程同步的, ...
- Tomcat和JavaWeb目录和流程
Tomcat主要目录结构 bin 二进制可执行文件,包含启动和关闭tomcat文件 conf 配置文件,其中包含了server.xml.context.xml.web.xml等 webapps 存 ...
- 利用Swagger2自动生成对外接口的文档
一直以来做对外的接口文档都比较原始,基本上都是手写的文档传来传去,最近发现了一个新玩具,可以在接口上省去不少麻烦. swagger是一款方便展示的API文档框架.它可以将接口的类型最全面的展示给对方开 ...
- python3 爬去QQ音乐
import requests import re import json import os def get_name(singer): url = 'https://c.y.qq.com/soso ...
- Vue.js——60分钟组件快速入门
一.组件简介 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树: 那么什么是组件呢?组件可以扩展HT ...
- @Controller和@RestController之间的区别
1. Controller, RestController的共同点 都是用来表示Spring某个类的是否可以接收HTTP请求 2. Controller, RestController的不同点 @Co ...
- Azure Go Management SDK 中国版使用示例
简介 刚学习go几天,尝试调用Azure的SDK进行管理API的操作,基本思路是基于注册的AD Application信息生成token,然后再使用Token生成serviceClient,然后再进行 ...