对象属性拷贝工具的选择

日常开发中,我们经常会把一个Po转化为一个Dto,或者把一个Dto转化为Po。如果类属性多了,很容易写错和写漏,而且也很烦。我们急需工具来帮助我们省去这部分开发量。

用什么工具?

目前主流的方式大概有三种

反射设置类属性

以BeanUtil为例,我们只需把两个对象的引用传入进去,工具通过反射的原理拷贝对象的值。一键完成。

注解驱动代码生成

类似MapStruct(原理类似Lombok),添加一些注解比如 @Mapper
Java的Annotation Processor 会自动帮这个接口生成一个实现类,根据方法签名帮你写好这部分代码。你只需调用这个接口就可以完成类的转换

生成动态代理类

与第二种类似,区别在于用CGLib 生成了代理对象。将A转化为B的时候,调用 BeanCopier#copy(A,B)
实际上是调用了一个具有为A转化到B量身定制get&set指令的对象

从几个维度思考工具

性能

第一种性能极差,程序运行时还使用反射一般都挺差的。

第二种和第三种理论上不存在性能问题,因为代码或是字节码一早生成好了。

可移植性

第一种和第三种兼容Kotlin。

第二种可能不支持Kotlin(猜的,因为lombok不支持),因为Kt没有调用 Annatation Processor的过程。
image
当然肯定存在一些技巧来手动兼容一下,但是对于长远来说可能是个负担。

API友好度

第一个和第三个学习成本相当。第二个有比较多的注解还有一些表达式,友好度较差。看看一个生产的例子:

image

当我第一次看到这段代码的时候,内心是崩溃的。

  1. @Mapper : 新同事看到这个@Mapper,还误以为是MyBatis 的,并且也是接口类。
  2. 想象在我写一大堆sourse 和 target 的时候,没有代码提示,内心慌得一批。
  3. 居然还有expression这种这么神奇的操作,在字符串里写Java代码,如果代码复杂,想象不出来到时候将如何调试。

工具的通病

使用工具有一个现象就是隐藏了细节,也就是说我并不知道工具实现成什么样子。是不是就是我想要的,我如何确认?

一些不太合理的规矩

比如这个用来转换的对象一定得有一个public NoArgsConstructor
,否则就转换失败。但是kotlin的data class 本身不具有这些奇奇怪怪的东西,这样导致一定得在我的代码体现这么一块为了使用工具而写的奇奇怪怪的逻辑。

学习成本

对于同在一个项目组的开发,我引入了一个新的工具,这就意味着大家都得跟我一样对这个工具了如指掌。比如他面对名字相同类型不同的情况,比如他面对java.time.LocalDate/LocalTime/LocalDateTime的情况。 这个本来能够在代码体现的淋漓尽致的事情变得不可读和难以控制了。

做了太多的事

有可能他帮我做了太多的事情,例如我只想复制一部分属性,剩下的属性由其他的规则来生成。他会把我全部属性都复制了,完了我还得设置一次新的值。一个字段的值被设置两次,如果你代码这么写,那肯定是不能容忍的。

当然,你可以调用其蹩脚的API来指挥它。比如把你不需要传的字段当做字符串传给他。这里没有代码提示,写错的风险是很高的。如果20个字段有10个是要拷贝的,有10个是不要的,想想一下那个画面…我还不如手写呢。

当我有特殊需求的时候

也有可能他不能帮我做全部的事情,例如我想把一个手机号的字段隐藏后四位,我必须调用一个通用的拷贝,再设置多一次特殊的值,把这两个设置的指令分散开。但是这就违背了我想调用一个方法来帮我完成赋值的初衷了。

或者你可以传入很多个自定义converter来让它变得更智能,能帮你完成一些你想让它做的事情。前提是,你得用它的协议,用它听得懂的话。那么这样带来的不仅是学习成本,还有调试成本,你根本不知道它内部如何来运营你的converter。这跟上面使用expression 带来的不友好度是一样的。

解决办法

隐藏这部分get&set细节是没有必要的,如果你觉得它碍眼,只需要把它放到另一个类里面冷藏起来。原本我只是懒得写还有可能写错,如果我努力一点,勤奋一点,应该是可以解决的,而使用上面的工具带来的新的问题可能比旧的问题更邪恶。

手写

本来没有问题,唯独剩下工作量还有设置遗漏的问题。

工具 + 手写

以工具为主,手写为辅。

在适当的时候使用工具,在不适当的时候手写。比较友好的就是用接口类,特殊的需求使用default方法来完成设置,而不是面向注解开发。
一味的面向注解开发给我的感觉就像是给一个小学生身上背了一百斤的书包,感觉真是糟透了。虽然这个时候使用工具还是有学习成本等问题,但是已经好多了。

手写 + 工具

以手写为主,工具为辅。

这只是一个构想,但是是本篇文章的精髓

假如有个傻瓜机器人,能够根据我的方法签名(入参和出参)生成一堆非常简单的get&set指令。但是他又不帮我执行,仅仅是将代码提供给我,让我自己决定要不要用他的方案。
我只需将这部分代码贴进我的方法体里面,再锦上添花小修小补一下。

  1. 一切类型不匹配的,命名不一致的这个时候都会通过IDE爆红。我能够在编译期就发现了代码的问题并改正。
  2. 当只需要设置一部分属性,我可以选择不要的执行,一键删除。
  3. 或者我需要一些特殊的类型转换,我可以用我的自己的代码来完成这部分事情。
  4. 当我debug的时候,我可以清晰看到我写的每一句代码对结果的影响,没有任何隐喻。
  5. 甚至我不用担心设置遗漏,因为这个愚蠢的机器人不会犯错。
  6. 当然最完美的是我终于可以不用手写这么愚蠢的代码了,这明显就是工具应该做的事情!

总而言之,控制权还在我手上,而我又不用干活,也不用担心工具给我带来的负面影响,因为在我代码里面,体现出来的就是没有使用工具的。

当然这个傻瓜机器人最好的存在方式就是 Idea 插件。
想象一下这个使用场景,以Kotlin为例:

  1. 我们写好了一个空方法:
    image
  2. 快捷键调出 Generate 面板
    image
    3.选择最后的Get&Set Commmand。傻瓜机器人根据返回值生成代码。
    image
    4.修修补补一下,改正编译期错误。Perfect !!
    image