一切还要从我的上一家公司讲起,我的上一家公司是一家独角兽企业,说大不大说小不小的那种,公司氛围也是比较注重技术,我当时入职之后的第一课就是阅读我们 CTO 写的开发规范,其中他把 Java 项目的目录结构划分的非常清楚,大概是下面这个样子的:
我当时觉得这种分层结构划分的非常优雅与合理,当然以上只是一个大概,更细节的分层我没有表露,但是随之而来在实际中使用的过程中,这样一个三层结构也带来了一个问题,那就是对象传输非常麻烦,因为每层都会有一个特有的对象。
下面我举一个例子,假如我们要插入一个 User 对象,那么它可能要经过以下二次对象转换:
以上只是一个比较简单的例子,相信大家已经可以从中看出开发者们需要写一些很繁琐但是很没意思的代码(手动 get/set
),比如上图中的三个对象可能字段都是一模一样的,但却需要重复写两次 Convert
去进行对象的转换。
当然这样设计当然也有好处,那就是解耦,比如我们现在用的数据库是 Mysql
,如果要换成 Mongo
只需要把第二次对象转换的代码稍微修改一下即可,这也是这样设计的初衷。
这么一个繁琐的事难道就没有一些方案进行解决吗?当然有,一般来说有两种方式:
先来说第一种方式吧,无论 BeanUtil
是深拷贝还是浅拷贝,它对我们开发者来说细节都是不可见的,一旦某个字段赋值出现了问题,我们并没有办法去进行代码的排查,因为我们没办法查看对象转换的细节,JSONObject
也有相同的问题。
然后就是第二种方式,MapStruct
在前段时间是一个非常火的方案,它和 Lombok
非常类似,在代码编译期帮助我们去生成对象之间的转换代码,我们也可以通过 IDEA 的提示去查看编译后的代码,我司除了手动 get/set
代码,最多的就是使用 MapStruct 进行处理。
但是对我而言,MapStruct 还是有一些缺点,首先我的 IDEA 经常没有查看编译后代码的提示,其次就是如果转换对象的某个字段不一样的时候,需要学习 MapStruct 的一些用法进行处理,有一定的学习负担,反正我到现在都没学会😂。
最终我为了方便,决定自己开发一个 IDEA 插件并起名为 BeanMappingKey
,目前已经迭代到 1.X 版本,主要思想是通过插件的方式为我们需要转换的对象自动生成转换代码。
目前暂时有三种用法:
其中,第一种和第二种生成起来是差不多的,具体可见以下例子:
上图的示例,是我们选中一个 Class 类进行代码的生成,根据这个类是否是建造者模式,来生成对应风格的代码,生成之后的代码被拷贝到剪贴板上,可以自由粘贴。
第二种方式,对于我来说是更加常用的,因为转换代码往往是写在一个方法里面的,通过选中方法名匹配入参和返回值进行代码生成,类似下面的例子:
匹配的逻辑就是根据字段名进行匹配,匹配失败的话则会留空,同时不只支持一个入参,可以对多个入参进行匹配,就像下面这样:
以上就是我对这个插件的介绍了,各位读者如果有兴趣的话可以在 IDEA 上面下载上试试:
Windows
系统上安装: File
> Settings
> Plugins
> Browse repositories...
> Search for "BeanMappingKey"
> Install Plugin
MacOS
系统上安装: Preferences
> Settings
> Plugins
> Browse repositories...
> Search for "BeanMappingKey"
> Install Plugin
手动安装
: Preferences
> Plugins
> Install plugin from disk...
注:暂且只支持 2020 以上版本的 IDEA ,安装之后无需重启。
目前我的这个插件依然还在完善中,对于复杂类型支持的还不够完善,比如对象里面嵌套对象的情况,我打算下一阶段继续对这块痛点进行升级完善,下面是此插件的地址:
2022-06-06 更新:插件更新 2.0 版本,已经支持对象嵌套的生成,欢迎大家在 IDEA 中下载使用。
1
linxinyue 2022-07-04 13:52:15 +08:00 1
简单试用了一下,感觉还蛮不错的,后续继续用用,点赞~
|
2
dqzcwxb 2022-07-04 14:27:07 +08:00 1
BeanUtil / JSONObject 和 MapStruct 和 lombok 的问题一样,细节不可见
手动 get/set 的好处就是灵活度高而且享受语法校验,不会因为大小写之类的问题踩坑 加油,看好你 |
3
wolfie 2022-07-04 14:33:39 +08:00 1
类似 lombok getter/setter ,只不过提前生成代码。
迭代时会很难受 成员类型 /名称 修改、删减成员。 |
4
Leviathann 2022-07-04 14:37:23 +08:00
不知道 controller 到 service 为啥要额外转,command 或者 query 直接用不行吗
|
5
RookieRicardo OP @wolfie 迭代时候直接把删除重新生成即可。
|
6
lower 2022-07-04 14:44:46 +08:00 1
问下其他非 Java 语言也有 VO BO DTO PO 这些玩意么?
|
7
RookieRicardo OP @Leviathann controller 和 service 中间应该有一个门面层,做一些其他处理,举个例子:
1. 前端给你传一个数组,但是是以字符串+逗号分割的形式给你的,需要你自己转成数组,这里不能再 service 去转,service 只接受标准的入参。 2. 前端给你一个另一个模块的 name 查询,需要先转成 id 后在进入到本模块。 |
8
RookieRicardo OP @dqzcwxb 感谢 可以推荐给朋友或者同事,有任何问题可以提 issue 我会及时处理。
|
9
RookieRicardo OP @linxinyue 感谢支持 可以推荐给朋友或者同事,有任何问题可以提 issue 我会及时处理。
|
10
RookieRicardo OP @lower 有 我在用 kotlin 但是有的,当然 kotlin 属于 java 系,但是目前的比如 ts 也是有相关概念的,但是没有 java 分的那么清楚,因为 ts 的场景一般只需要一种标准 Object 即可,也就是说一般只需要转化一次。
|
11
TheCure 2022-07-04 15:06:20 +08:00
支持 kotlin 吗
|
12
zamaojava 2022-07-04 15:11:19 +08:00
如果可以添加字段的注释,就完美了。
|
13
zamaojava 2022-07-04 15:12:27 +08:00
基于 pojo 的注释,set 的时候自动加上,就太棒了。现阶段都是我不忙得时候,先生成,在一行行加上
|
14
q1angch0u 2022-07-04 15:14:00 +08:00
@lower 我认为其实 pojo 大体上说是为了解耦、优化代码逻辑用的,和用什么语言关系其实不太大,Java 比较注重这些的原因是体量比较大,相关的规范比较多,也就容易被大家拿出来说了。
|
15
RookieRicardo OP @TheCure 暂不支持 我现在公司项目就在写 kotlin ,讲真,kotlin 写起来更简单一点,感觉没必要用这个。
|
16
RookieRicardo OP @zamaojava 加注释是啥意思呢? 插件没办法知道你想要的注释内容是啥,难道是那几个 /**/ 符号吗😂
|
17
ql562482472 2022-07-04 15:27:33 +08:00 1
|
18
blless 2022-07-04 15:46:34 +08:00
@lower 其实也有的 VO BO DTO PO 是业务属性的东西,跟语言没关系。我之前看 go python 之类的,简单一点的业务就是 MVC 走天下。复杂一点比如 DDD 之类的,不管什么语言 你都会看到一堆的 VO DTO PO 之类的。
其实在我看来各种概念都是把复杂业务从横向纵向多个角度拆分,拆碎,留出足够的独立编码,测试,最后再继承的空间。 |
19
zamaojava 2022-07-04 15:49:33 +08:00
@RookieRicardo 对,获取不到吗?
//姓名 pojo.setName(dto.getName()); //密码 pojo.setPwd(dto.getPwd()) 我觉得很舒服啊。pojon 实体类,一般都有注释的 |
20
zamaojava 2022-07-04 15:51:22 +08:00
我一直用的是 generateallsetter 这个插件,op 主这个好像功能和他差不多
|
21
A555 2022-07-04 15:53:07 +08:00
差不多,之前是写了个泛型的方法,处理一些结构一模一样的 bean 类转换
|
22
Leviathann 2022-07-04 16:23:05 +08:00
@RookieRicardo 现在 web 框架应该都有把 querystring 自动转成复合对象的功能
|
23
pengtdyd 2022-07-04 19:06:32 +08:00
《比如我们现在用的数据库是 Mysql ,如果要换成 Mongo 》在实际的项目开发中,99.99%不会涉及需要换数据库的情况。过度的设计是软件开发中的大忌,软件产品的生命周期只有 18 个月,如此冗余的设计会让开发者不堪重负!
|
24
RookieRicardo OP @ql562482472 这个并不支持方法自动匹配生成,所以我才自己写了。
|
25
RookieRicardo OP @Leviathann 请求参数转对象都是可以的,这个主要是内部的 DTO 转换。
|
26
RookieRicardo OP @pengtdyd 那我换个例子吧,其实我觉得我们没必要争论这个,这里可以不是数据库而是远程接口,之前我们都是在这一层接远程接口用的,远程接口的参数变化是常见的。
|
27
RookieRicardo OP @zamaojava 这个并不支持方法匹配入参和出参生成,所以我才自己写了。
|
28
zamaojava 2022-07-04 19:47:21 +08:00
@RookieRicardo 可以啊,只是文档没写,而且技巧很隐蔽
|
29
RookieRicardo OP @zamaojava 哈哈 文章里面有说 不过是在中间说的 看来我应该加粗一下
|
30
SimpleSS 2022-07-04 22:45:16 +08:00
好用,赞一个,干了我一直想干的一件事,虽然我们是 netty 的项目,也经常要写一堆 model ,这样生产不容易漏字段
|
31
Jooooooooo 2022-07-04 22:52:28 +08:00
正好需要这个, 我来试试看.
|
33
russ44 2022-07-05 11:17:35 +08:00
试用一下
|
34
justin2018 2022-07-06 07:47:55 +08:00
好东西 安装了 😁
|
35
Asimov01 2022-07-06 11:01:56 +08:00
很棒,感谢贡献 🎉
|
36
RookieRicardo OP @SimpleSS 好用的话可以向同事推荐一下
|
37
RookieRicardo OP @Asimov01 能帮到大家就好
|
38
yveJohn 2022-07-06 18:00:09 +08:00
个人认为如果可以加上集合对象的转换就更加完美了,例如 List<PO> -> List<DTO>
|
39
siweipancc 2022-07-07 09:08:49 +08:00 via iPhone
idea 新版本的 JPA 插件已经默认集成了这个功能了 b
|
40
ychost 2022-07-07 09:24:52 +08:00
我还是喜欢 BeanUtils 一把梭,深拷贝就用 JSON 序列化,一般深拷贝用的比较少,除非领域模型真的非常重,嵌套非常多
|
41
mosliu 2022-07-07 16:01:18 +08:00
使用看看
|
42
RookieRicardo OP @siweipancc 你说的是通过数据库生成 DTO 吧?
|
43
RookieRicardo OP @yveJohn 原来确实有这个计划 但是时间场景你会写一个 convert 函数,然后 list 循环调用的
|
44
RookieRicardo OP @ychost JSON 序列化除了不好排查错误 还有一个问题就是性能没有这种方式好,虽然这点性能也无所谓
|
45
mitsuizzz 2022-07-08 09:40:45 +08:00
CTO 不会是阿里出来的吧
|
46
RookieRicardo OP @mitsuizzz 这我倒是不知道 不过他现在已经是 CEO 了
|
47
siweipancc 2022-07-08 12:08:35 +08:00 via iPhone
@RookieRicardo 有的,通过 Entity 直接生成,插件 tab 就能进去
|
48
ZiLong 2022-07-08 22:37:54 +08:00
赞
|
49
RookieRicardo OP @siweipancc 哦哦 那我知道了 这还是两个领域的,我做的不是为了它那种效果的。
|
50
rfrftt 2022-07-15 17:14:56 +08:00
因为新的需求规范,正好用上了
|
51
dayudayupao 2022-07-25 15:01:57 +08:00
@zamaojava 这个注释有点拉。。。实体里面有注释就好了,不然你实体用在十个地方,注释也写十遍吗,我点到实体类去看不就行啦?
|
52
zamaojava 2022-07-25 15:19:41 +08:00
@dayudayupao 等你维护 别人的 dto 的时候你就知道有注释和没注释的区别了
|
53
dayudayupao 2022-07-26 17:52:31 +08:00
@zamaojava 我不是说实体里面有注释吗,不明白生成 set 代码的地方加注释有什么特别的方便之处,你举个例子?
|