为了解决 Java 对象转换的痛点,我开发了一款 IDEA 插件 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
RookieRicardo
V2EX    分享创造

为了解决 Java 对象转换的痛点,我开发了一款 IDEA 插件

  •  
  •   RookieRicardo 2022-07-04 13:28:48 +08:00 6404 次点击
    这是一个创建于 1263 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景

    一切还要从我的上一家公司讲起,我的上一家公司是一家独角兽企业,说大不大说小不小的那种,公司氛围也是比较注重技术,我当时入职之后的第一课就是阅读我们 CTO 写的开发规范,其中他把 Java 项目的目录结构划分的非常清楚,大概是下面这个样子的:
    image.png
    我当时觉得这种分层结构划分的非常优雅与合理,当然以上只是一个大概,更细节的分层我没有表露,但是随之而来在实际中使用的过程中,这样一个三层结构也带来了一个问题,那就是对象传输非常麻烦,因为每层都会有一个特有的对象。

    下面我举一个例子,假如我们要插入一个 User 对象,那么它可能要经过以下二次对象转换:
    image.png

    以上只是一个比较简单的例子,相信大家已经可以从中看出开发者们需要写一些很繁琐但是很没意思的代码(手动 get/set),比如上图中的三个对象可能字段都是一模一样的,但却需要重复写两次 Convert 去进行对象的转换。

    当然这样设计当然也有好处,那就是解耦,比如我们现在用的数据库是 Mysql,如果要换成 Mongo 只需要把第二次对象转换的代码稍微修改一下即可,这也是这样设计的初衷。

    常见方案

    这么一个繁琐的事难道就没有一些方案进行解决吗?当然有,一般来说有两种方式:

    1. BeanUtil / JSONObject
    2. MapStruct

    先来说第一种方式吧,无论 BeanUtil 是深拷贝还是浅拷贝,它对我们开发者来说细节都是不可见的,一旦某个字段赋值出现了问题,我们并没有办法去进行代码的排查,因为我们没办法查看对象转换的细节,JSONObject 也有相同的问题。

    然后就是第二种方式,MapStruct 在前段时间是一个非常火的方案,它和 Lombok 非常类似,在代码编译期帮助我们去生成对象之间的转换代码,我们也可以通过 IDEA 的提示去查看编译后的代码,我司除了手动 get/set 代码,最多的就是使用 MapStruct 进行处理。

    但是对我而言,MapStruct 还是有一些缺点,首先我的 IDEA 经常没有查看编译后代码的提示,其次就是如果转换对象的某个字段不一样的时候,需要学习 MapStruct 的一些用法进行处理,有一定的学习负担,反正我到现在都没学会。

    我的方案

    最终我为了方便,决定自己开发一个 IDEA 插件并起名为 BeanMappingKey,目前已经迭代到 1.X 版本,主要思想是通过插件的方式为我们需要转换的对象自动生成转换代码。

    目前暂时有三种用法:

    1. 根据对象生成对应的 get/set 方法,支持建造者模式。
    2. 根据 Class 生成对应的 get/set 方法,支持建造者模式。
    3. 根据一个方法的入参和返回值,进行对象的转换代码生成,支持建造者模式。

    其中,第一种和第二种生成起来是差不多的,具体可见以下例子:
    GIF 2022-4-4 18-08-30.gif
    GIF 2022-1-1 17-31-53.gif
    上图的示例,是我们选中一个 Class 类进行代码的生成,根据这个类是否是建造者模式,来生成对应风格的代码,生成之后的代码被拷贝到剪贴板上,可以自由粘贴。

    第二种方式,对于我来说是更加常用的,因为转换代码往往是写在一个方法里面的,通过选中方法名匹配入参和返回值进行代码生成,类似下面的例子:
    GIF 2022-1-1 17-29-24.gif
    匹配的逻辑就是根据字段名进行匹配,匹配失败的话则会留空,同时不只支持一个入参,可以对多个入参进行匹配,就像下面这样:
    GIF 2022-4-4 20-35-56.gif


    以上就是我对这个插件的介绍了,各位读者如果有兴趣的话可以在 IDEA 上面下载上试试:

    • Windows 系统上安装:
      • File > Settings > Plugins > Browse repositories... > Search for "BeanMappingKey" > Install Plugin
    • MacOS 系统上安装:
      • Preferences > Settings > Plugins > Browse repositories... > Search for "BeanMappingKey" > Install Plugin
    • 手动安装:
      • 下载 latest release 之后, 选择 Preferences > Plugins > Install plugin from disk...

    注:暂且只支持 2020 以上版本的 IDEA ,安装之后无需重启。

    目前我的这个插件依然还在完善中,对于复杂类型支持的还不够完善,比如对象里面嵌套对象的情况,我打算下一阶段继续对这块痛点进行升级完善,下面是此插件的地址:


    2022-06-06 更新:插件更新 2.0 版本,已经支持对象嵌套的生成,欢迎大家在 IDEA 中下载使用。

    53 条回复    2022-07-26 17:52:31 +8:00
    M1234
        1
    M1234  
       2022-07-04 13:52:15 +08:00   1
    简单试用了一下,感觉还蛮不错的,后续继续用用,点赞~
    dqzcwxb
        2
    dqzcwxb  
       2022-07-04 14:27:07 +08:00   1
    BeanUtil / JSONObject 和 MapStruct 和 lombok 的问题一样,细节不可见
    手动 get/set 的好处就是灵活度高而且享受语法校验,不会因为大小写之类的问题踩坑

    加油,看好你
    wolfie
        3
    wolfie  
       2022-07-04 14:33:39 +08:00   1
    类似 lombok getter/setter ,只不过提前生成代码。
    迭代时会很难受 成员类型 /名称 修改、删减成员。
    Leviathann
        4
    Leviathann  
       2022-07-04 14:37:23 +08:00
    不知道 controller 到 service 为啥要额外转,command 或者 query 直接用不行吗
    RookieRicardo
        5
    RookieRicardo  
    OP
       2022-07-04 14:42:46 +08:00
    @wolfie 迭代时候直接把删除重新生成即可。
    lower
        6
    lower  
       2022-07-04 14:44:46 +08:00   1
    问下其他非 Java 语言也有 VO BO DTO PO 这些玩意么?
    RookieRicardo
        7
    RookieRicardo  
    OP
       2022-07-04 14:45:01 +08:00
    @Leviathann controller 和 service 中间应该有一个门面层,做一些其他处理,举个例子:
    1. 前端给你传一个数组,但是是以字符串+逗号分割的形式给你的,需要你自己转成数组,这里不能再 service 去转,service 只接受标准的入参。
    2. 前端给你一个另一个模块的 name 查询,需要先转成 id 后在进入到本模块。
    RookieRicardo
        8
    RookieRicardo  
    OP
       2022-07-04 14:45:41 +08:00
    @dqzcwxb 感谢 可以推荐给朋友或者同事,有任何问题可以提 issue 我会及时处理。
    RookieRicardo
        9
    RookieRicardo  
    OP
       2022-07-04 14:45:53 +08:00
    @linxinyue 感谢支持 可以推荐给朋友或者同事,有任何问题可以提 issue 我会及时处理。
    RookieRicardo
        10
    RookieRicardo  
    OP
       2022-07-04 14:47:31 +08:00
    @lower 有 我在用 kotlin 但是有的,当然 kotlin 属于 java 系,但是目前的比如 ts 也是有相关概念的,但是没有 java 分的那么清楚,因为 ts 的场景一般只需要一种标准 Object 即可,也就是说一般只需要转化一次。
    TheCure
        11
    TheCure  
       2022-07-04 15:06:20 +08:00
    支持 kotlin 吗
    zamaojava
        12
    zamaojava  
       2022-07-04 15:11:19 +08:00
    如果可以添加字段的注释,就完美了。
    zamaojava
        13
    zamaojava  
       2022-07-04 15:12:27 +08:00
    基于 pojo 的注释,set 的时候自动加上,就太棒了。现阶段都是我不忙得时候,先生成,在一行行加上
    ryanbuu
        14
    ryanbuu  
       2022-07-04 15:14:00 +08:00
    @lower 我认为其实 pojo 大体上说是为了解耦、优化代码逻辑用的,和用什么语言关系其实不太大,Java 比较注重这些的原因是体量比较大,相关的规范比较多,也就容易被大家拿出来说了。
    RookieRicardo
        15
    RookieRicardo  
    OP
       2022-07-04 15:20:36 +08:00
    @TheCure 暂不支持 我现在公司项目就在写 kotlin ,讲真,kotlin 写起来更简单一点,感觉没必要用这个。
    RookieRicardo
        16
    RookieRicardo  
    OP
       2022-07-04 15:21:52 +08:00
    @zamaojava 加注释是啥意思呢? 插件没办法知道你想要的注释内容是啥,难道是那几个 /**/ 符号吗
    ql562482472
        17
    ql562482472  
       2022-07-04 15:27:33 +08:00   1
    blless
        18
    blless  
       2022-07-04 15:46:34 +08:00
    @lower 其实也有的 VO BO DTO PO 是业务属性的东西,跟语言没关系。我之前看 go python 之类的,简单一点的业务就是 MVC 走天下。复杂一点比如 DDD 之类的,不管什么语言 你都会看到一堆的 VO DTO PO 之类的。
    其实在我看来各种概念都是把复杂业务从横向纵向多个角度拆分,拆碎,留出足够的独立编码,测试,最后再继承的空间。
    zamaojava
        19
    zamaojava  
       2022-07-04 15:49:33 +08:00
    @RookieRicardo 对,获取不到吗?

    //姓名
    pojo.setName(dto.getName());
    //密码
    pojo.setPwd(dto.getPwd())


    我觉得很舒服啊。pojon 实体类,一般都有注释的
    zamaojava
        20
    zamaojava  
       2022-07-04 15:51:22 +08:00
    我一直用的是 generateallsetter 这个插件,op 主这个好像功能和他差不多
    A555
        21
    A555  
       2022-07-04 15:53:07 +08:00
    差不多,之前是写了个泛型的方法,处理一些结构一模一样的 bean 类转换
    Leviathann
        22
    Leviathann  
       2022-07-04 16:23:05 +08:00
    @RookieRicardo 现在 web 框架应该都有把 querystring 自动转成复合对象的功能
    pengtdyd
        23
    pengtdyd  
       2022-07-04 19:06:32 +08:00
    《比如我们现在用的数据库是 Mysql ,如果要换成 Mongo 》在实际的项目开发中,99.99%不会涉及需要换数据库的情况。过度的设计是软件开发中的大忌,软件产品的生命周期只有 18 个月,如此冗余的设计会让开发者不堪重负!
    RookieRicardo
        24
    RookieRicardo  
    OP
       2022-07-04 19:18:25 +08:00
    @ql562482472 这个并不支持方法自动匹配生成,所以我才自己写了。
    RookieRicardo
        25
    RookieRicardo  
    OP
       2022-07-04 19:19:20 +08:00
    @Leviathann 请求参数转对象都是可以的,这个主要是内部的 DTO 转换。
    RookieRicardo
        26
    RookieRicardo  
    OP
       2022-07-04 19:21:05 +08:00
    @pengtdyd 那我换个例子吧,其实我觉得我们没必要争论这个,这里可以不是数据库而是远程接口,之前我们都是在这一层接远程接口用的,远程接口的参数变化是常见的。
    RookieRicardo
        27
    RookieRicardo  
    OP
       2022-07-04 19:21:43 +08:00
    @zamaojava 这个并不支持方法匹配入参和出参生成,所以我才自己写了。
    zamaojava
        28
    zamaojava  
       2022-07-04 19:47:21 +08:00
    @RookieRicardo 可以啊,只是文档没写,而且技巧很隐蔽
    RookieRicardo
        29
    RookieRicardo  
    OP
       2022-07-04 21:22:27 +08:00
    @zamaojava 哈哈 文章里面有说 不过是在中间说的 看来我应该加粗一下
    SimpleSS
        30
    SimpleSS  
       2022-07-04 22:45:16 +08:00
    好用,赞一个,干了我一直想干的一件事,虽然我们是 netty 的项目,也经常要写一堆 model ,这样生产不容易漏字段
    Jooooooooo
        31
    Jooooooooo  
       2022-07-04 22:52:28 +08:00
    正好需要这个, 我来试试看.
    cubecube
        32
    cubecube  
       2022-07-04 23:06:45 +08:00
    @zamaojava 我记得生成一个对象,需要手动改下命名吧
    russ44
        33
    russ44  
       2022-07-05 11:17:35 +08:00
    试用一下
    justin2018
        34
    justin2018  
       2022-07-06 07:47:55 +08:00
    好东西 安装了
    Asimov01
        35
    Asimov01  
       2022-07-06 11:01:56 +08:00
    很棒,感谢贡献
    RookieRicardo
       36
    RookieRicardo  
    OP
       2022-07-06 11:45:15 +08:00
    @SimpleSS 好用的话可以向同事推荐一下
    RookieRicardo
        37
    RookieRicardo  
    OP
       2022-07-06 11:46:37 +08:00
    @Asimov01 能帮到大家就好
    yveJohn
        38
    yveJohn  
       2022-07-06 18:00:09 +08:00
    个人认为如果可以加上集合对象的转换就更加完美了,例如 List<PO> -> List<DTO>
    siweipancc
        39
    siweipancc  
       2022-07-07 09:08:49 +08:00 via iPhone
    idea 新版本的 JPA 插件已经默认集成了这个功能了 b
    ychost
        40
    ychost  
       2022-07-07 09:24:52 +08:00
    我还是喜欢 BeanUtils 一把梭,深拷贝就用 JSON 序列化,一般深拷贝用的比较少,除非领域模型真的非常重,嵌套非常多
    mosliu
        41
    mosliu  
       2022-07-07 16:01:18 +08:00
    使用看看
    RookieRicardo
        42
    RookieRicardo  
    OP
       2022-07-07 17:14:22 +08:00
    @siweipancc 你说的是通过数据库生成 DTO 吧?
    RookieRicardo
        43
    RookieRicardo  
    OP
       2022-07-07 17:14:53 +08:00
    @yveJohn 原来确实有这个计划 但是时间场景你会写一个 convert 函数,然后 list 循环调用的
    RookieRicardo
        44
    RookieRicardo  
    OP
       2022-07-07 17:16:00 +08:00
    @ychost JSON 序列化除了不好排查错误 还有一个问题就是性能没有这种方式好,虽然这点性能也无所谓
    mitsuizzz
        45
    mitsuizzz  
       2022-07-08 09:40:45 +08:00
    CTO 不会是阿里出来的吧
    RookieRicardo
        46
    RookieRicardo  
    OP
       2022-07-08 09:41:20 +08:00
    @mitsuizzz 这我倒是不知道 不过他现在已经是 CEO 了
    siweipancc
        47
    siweipancc  
       2022-07-08 12:08:35 +08:00 via iPhone
    @RookieRicardo 有的,通过 Entity 直接生成,插件 tab 就能进去
    ZiLong
        48
    ZiLong  
       2022-07-08 22:37:54 +08:00
    RookieRicardo
        49
    RookieRicardo  
    OP
       2022-07-12 20:12:50 +08:00
    @siweipancc 哦哦 那我知道了 这还是两个领域的,我做的不是为了它那种效果的。
    rfrftt
        50
    rfrftt  
       2022-07-15 17:14:56 +08:00
    因为新的需求规范,正好用上了
    dayudayupao
        51
    dayudayupao  
       2022-07-25 15:01:57 +08:00
    @zamaojava 这个注释有点拉。。。实体里面有注释就好了,不然你实体用在十个地方,注释也写十遍吗,我点到实体类去看不就行啦?
    zamaojava
        52
    zamaojava  
       2022-07-25 15:19:41 +08:00
    @dayudayupao 等你维护 别人的 dto 的时候你就知道有注释和没注释的区别了
    dayudayupao
        53
    dayudayupao  
       2022-07-26 17:52:31 +08:00
    @zamaojava 我不是说实体里面有注释吗,不明白生成 set 代码的地方加注释有什么特别的方便之处,你举个例子?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1168 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 35ms UTC 17:47 PVG 01:47 LAX 09:47 JFK 12:47
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86